<html>
<head>
  <title>Evernote Export</title>
  <basefont face="微软雅黑" size="2" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="exporter-version" content="YXBJ Windows/602628 (zh-CN, DDL); Windows/10.0.0 (Win64); EDAMVersion=V2;"/>
  <style>
    body, td {
      font-family: 微软雅黑;
      font-size: 10pt;
    }
  </style>
</head>
<body>
<a name="19069"/>
<h1>0. 图例含义及代码实现</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/3/11 17:30</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2020/5/18 6:12</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div>在深入学习设计模式之前，需要仔细研究一下在设计模式书中出现的各种图例，从代码实现的层次上研究一下。</div><div>比如都知道所谓的类图之间的关系有关联、依赖、组合、聚合等，但是这些名词其实并不理解，只有从代码实现上理解了这些关系，才能真正看懂类图。</div><div><br/></div><div>《设计模式：可复用面向对象软件的基础》这本书中的各种图例使用的是OMT表示法，其他的书籍，如《大话设计模式》、《设计模式之禅》使用的是UML，这两种表示方法不太相同，主要区别在类图上，这也是我应该着重研究的。</div><div><br/></div><div>一、OMT表示法的图例</div><div>在《设计模式：可复用面向对象软件的基础》这本书中，总共有三类图例，类图、对象图、交互图。</div><div><span style="font-weight: bold;">1.交互图</span></div><div><img src="0.设计模式专题_files/Image.png" type="image/png" data-filename="Image.png" width="1094"/></div><div>这里的交互图其实就是UML中的时序图，这个图能够看懂，没什么说的。</div><div><span style="font-weight: bold;">2.类图</span></div><div>类图有些复杂了：</div><div><span style="font-weight: bold;">（1）抽象类与具体类</span></div><div>斜体表示类或者方法是抽象的。</div><div><img src="0.设计模式专题_files/Image [1].png" type="image/png" data-filename="Image.png" width="1241"/></div><div><span style="font-weight: bold;">（2）参与客户类与绝对客户类</span></div><div><img src="0.设计模式专题_files/Image [2].png" type="image/png" data-filename="Image.png" width="801"/></div><div>参与客户类：客户是设计模式的参与者（即该客户类在这个模式中承担一定的责任）。</div><div>绝对客户类：客户不是设计模式的参与者（即客户类在模式中不承担责任）。</div><div><span style="font-weight: bold;">（3）类关系</span></div><div>这是类图的重点，类的关系有：类继承、部分或聚集关系的对象引用、相识关系、创建关系、多余一个的聚集关系等。</div><div><img src="0.设计模式专题_files/Image [3].png" type="image/png" data-filename="Image.png" width="1425"/></div><div>这里的重点就是把类的关系搞懂。</div><div><span style="font-weight: bold;">a)实例化</span></div><div><img src="0.设计模式专题_files/Image [4].png" type="image/png" data-filename="Image.png" width="844"/></div><div>虚箭头线表示一个类实例化另一个类的对象，箭头指向被实例化的对象的类。</div><div>Instantiator类实例化Instantiatee类的对象，Instantiator类不拥有Instantiatee类的对象，只是创建一下。（考虑工厂模式）</div><div><span style="font-weight: bold;">b)继承</span></div><div><img src="0.设计模式专题_files/Image [5].png" type="image/png" data-filename="Image.png" width="322"/></div><div>类subclass继承于parentclass，箭头指向的类是父类。</div><div><span style="font-weight: bold;">c)相识</span></div><div><img src="0.设计模式专题_files/Image [6].png" type="image/png" data-filename="Image.png" width="1223"/></div><div>箭头线表示一个类对另一个类实例的引用关系。</div><div>个人理解，通过指针或者引用实现，Window类中，通过指针或者引用使用了Rectangle类的对象。（但应该不是通过成员函数使用的）</div><div><span style="font-weight: bold;">d)聚合</span></div><div><img src="0.设计模式专题_files/Image [7].png" type="image/png" data-filename="Image.png" width="993"/></div><div>聚合是定义真正实例的成员变量，或者将这些成员变量定义为实例指针或引用。</div><div>Aggregator类中，定义了Agregatee类的对象。</div><div><span style="font-style: italic;">注意，不一定是成员变量，可能是成员函数中使用到了另一个类的对象。</span></div><div><span style="font-weight: bold;">注意：</span>聚合与相识容易混淆，个人认为，聚合的表现是成员变量，相识的表现只是在一个类中使用到了另外的一个类，如何使用可能是成员函数的一个参数（对不对？）</div><div>又或者是聚合表示一个成员对象，委托表示成员引用或指针（64页）？</div><div><span style="font-weight: bold;">e)多于一个</span></div><div><img src="0.设计模式专题_files/Image [8].png" type="image/png" data-filename="Image.png" width="1425"/></div><div>图中，Drawing和Shape之间的关系中，有一个实心圆点，表示“多于一个”，说明Drawing类中聚合了多于一个的Shape，即，Drawing类中定义了多个Shape类的对象作为成员变量，成员变量的名称是shapes。</div><div><span style="font-weight: bold;">（4）伪代码注解</span></div><div>简要说明方法的实现。</div><div><img src="0.设计模式专题_files/Image [9].png" type="image/png" data-filename="Image.png" width="876"/></div><div><span style="font-weight: bold;">3.对象图</span></div><div><img src="0.设计模式专题_files/Image [10].png" type="image/png" data-filename="Image.png" width="812"/></div><div>书上的说法：对象图仅仅描述实例。它描述了设计模式中的对象某个时刻的状况。对象的名字通常表示为aSomething，其中，Something是该对象的类。表示对象的符号是一个圆角矩形，并以一条直线将对象名与对象引用分开。箭头表示对象引用。</div><div>个人理解：也就是说，上图的三个圆角矩形表示某一个时刻的类的对象，途中的意思是，有一个Drawing类的对象，对象中，有两个成员shape[0]和shape[1]，每一个成员指向另一个对象，shape[0]是CircleShape类的一个对象的引用，shape[1]是LineShape对象的引用。</div><div><br/></div><div>二、UML表示法的图例</div><div>对于UML，我不太明白的只有类图中的各种关系如何具体实现，所以这里仅对类图进行讨论。</div><div>以下描述摘自《大话设计模式》一书。</div><div>一个典型的类图：</div><div><img src="0.设计模式专题_files/Image [11].png" type="image/png" data-filename="Image.png" width="1179"/></div><div>其中，有以下几个类图元素：</div><div><span style="font-weight: bold;">1）类图</span></div><div><img src="0.设计模式专题_files/Image [12].png" type="image/png" data-filename="Image.png" width="530"/></div><div>类图分为三层：</div><ul><li><div>类名，如果是抽象类，则用斜体表示。</div></li><li><div>成员变量，</div></li><li><div>成员函数，'+'表示public，'-'表示private，'#'表示protected</div></li></ul><div><span style="font-weight: bold;">2）接口</span></div><div><img src="0.设计模式专题_files/Image [13].png" type="image/png" data-filename="Image.png" width="1453"/></div><div>接口有两种表示方法。</div><div>另外，Java中有明确的接口，C++中接口的表示是不是虚基类？估计在C++中很难区分接口和父类。（没有明确的语法设施）</div><div><span style="font-weight: bold;">3）继承</span></div><div><img src="0.设计模式专题_files/Image [14].png" type="image/png" data-filename="Image.png" width="501"/></div><div>继承关系用空心三角形+实线来表示。   </div><div>鸟继承于动物，动物是一个抽象类，鸟是动物的子类。</div><div><img src="0.设计模式专题_files/Image [15].png" type="image/png" data-filename="Image.png" width="690"/></div><div><span style="font-weight: bold;">4）接口实现</span></div><div><img src="0.设计模式专题_files/Image [16].png" type="image/png" data-filename="Image.png" width="386"/></div><div>接口实现使用空心三角形+虚线表示。</div><div>大雁实现飞翔的接口。</div><div><img src="0.设计模式专题_files/Image [17].png" type="image/png" data-filename="Image.png" width="676"/></div><div><span style="font-weight: bold;">5）关联关系（association）</span></div><div><img src="0.设计模式专题_files/Image [18].png" type="image/png" data-filename="Image.png" width="444"/></div><div>关联关系表示一个类知道另一个类，实际上就是箭头指向的类的对象是箭头发源的类的一个成员变量。气候是企鹅的成员变量。</div><div>线段两端可能有基数（见合成关系描述）。</div><div><img src="0.设计模式专题_files/Image [19].png" type="image/png" data-filename="Image.png" width="1423"/></div><div><span style="font-weight: bold;">6）聚合关系（Aggregation）</span></div><div><img src="0.设计模式专题_files/Image [20].png" type="image/png" data-filename="Image.png" width="419"/></div><div>聚合表示一种弱的拥有关系，体现的是A对象可以包含B对象，但B对象不是A对象的一部分。</div><div>在上图的例子中，大雁是群居动物，每只大雁都属于一个雁群，一个雁群可以有多只大雁。</div><div>线段两端可能有基数（见合成关系描述）。</div><div><img src="0.设计模式专题_files/Image [21].png" type="image/png" data-filename="Image.png" width="1413"/></div><div><span style="font-weight: bold;">7）合成（组合）关系（Composition）</span></div><div><img src="0.设计模式专题_files/Image [22].png" type="image/png" data-filename="Image.png" width="726"/></div><div>合成是一种强的拥有关系，体现了严格的部分和整体的关系，部分和整体的生命周期一样。</div><div>合成关系的连线两端还有一个数字，这被称为基数，表明这一端的类可以有几个实例。如果一个类可能有无数个实例，则用n来表示。</div><div>上图示例中，鸟和翅膀就是合成关系，因为它们是部分和整体的关系，并且翅膀和鸟的声明周期是相同的！</div><div><img src="0.设计模式专题_files/Image [23].png" type="image/png" data-filename="Image.png" width="1451"/></div><div>注意：关联、聚合、组合这三种关系之间的区别不容易区分。个人理解：</div><ul><li><div>关联关系是一个类的对象是另一个类的成员变量，二者生命周期不做要求（这一点是与组合关系的区别）；</div></li><li><div>聚合关系是一个类的对象是另一个类的成员变量数组，二者生命周期不做要求；</div></li><li><div>组合关系是一个类的对象是另一个类的成员变量，二者生命周期必须一致。</div></li></ul><div>8）依赖关系</div><div><img src="0.设计模式专题_files/Image [24].png" type="image/png" data-filename="Image.png" width="877"/></div><div>依赖关系一般体现为只是在类中的成员函数中使用到，并不是类的一个成员变量。</div><div><img src="0.设计模式专题_files/Image [25].png" type="image/png" data-filename="Image.png" width="1419"/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="19951"/>
<h1>2. 建造者（Builder）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/4/5 9:33</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/10 23:57</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div><span style="font-weight: bold;">将一个复杂对象的构建与它的表示分离，使得同样的构建过程可以创建不同的表示。</span></div></li><li><div>建造者模式可以将一个产品的内部表象与产品的生成过程分割开来，从而可以使一个建造过程生成具有不同的内部表象的产品对象，如果使用建造者模式，用户就只需要指定需建造的类型就可以得到它们，而具体建造过程和细节就不需要知道了。</div></li><li><div>建造者模式主要解决<span style="font-weight: bold;">如何一步步创建一个包含多个组成部分的复杂对象。</span></div></li><li><div>客户不需要知道复杂对象的内部组成部分与装配方式，只需要知道所需建造者的类型即可。</div></li><li><div>建造者模式是逐步建造产品的，所以<span style="font-weight: bold;">建造者的Builder类里的那些建造方法必须足够普遍</span>，以便为各种类型的具体建造者构造。这是一个权衡的过程，如果有些细节，有些过程是每个具体的建造者都需要构建的，那就应该要加入基类Builder类中作为一个通用的建造方法。</div></li><li><div>建造者模式中还有一个角色，是<span style="font-weight: bold;">零件</span>，建造者通过零件的组装去建造一个对象，组装顺序不同，对象效能也不同。</div></li><li><div>使用生成器模式封装一个产品的构造过程，并允许按步骤构造。</div></li><li><div>经常被用来创建组合结构。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div><img src="0.设计模式专题_files/Image [26].png" type="image/png" data-filename="Image.png" width="426"/></div><ul><li><div><span style="font-weight: bold;">Builder：</span>为创建一个Product对象的各个部件指定抽象的接口。<span style="font-style: italic;">（提供抽象、提供接口）</span></div></li><li><div><span style="font-weight: bold;">Director：</span>构造一个使用Builder接口的对象。<span style="font-style: italic;">（组装零件，只使用Builder提供的接口）</span></div></li><li><div><span style="font-weight: bold;">ConcreteBuilder：</span>1）实现Builder的接口以构造和装配该产品的各个部件；2）定义并跟踪它所创建的表示；3）提供一个检索产品的接口（这个接口也可以放到Builder当中）<span style="font-style: italic;">（生产零件）</span></div></li><li><div><span style="font-weight: bold;">Product：</span>表示被构造的复杂对象。<span style="font-style: italic;">（多零件组成的复杂产品）</span></div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点：</span></div><ul><li><div>封装性：使用建造者模式可以使客户端不必知道产品内部组成的细节。</div></li><li><div>每个具体建造者相互独立，可以很方便的替换具体建造者，容易扩展。</div></li><li><div>便于控制细节风险，可对构造过程进行更精细的控制。</div></li><li><div>将一个复杂对象的创建封装起来。允许对象通过多个步骤来创建，并且可以改变过程。</div></li><li><div>向客户隐藏产品内部的表现。</div></li><li><div>产品的实现可以被替换，因为客户只看到一个抽象的接口。</div></li></ul><div><span style="font-weight: bold;">缺点：</span></div><ul><li><div>建造者模式所创建的产品一般具有较多的共同点，其组成部分相似，如果产品之间差异性很大，例如很多组成部分都不相同，不适合使用建造者模式，因此范围受的一定的限制。</div></li><li><div>如果产品的内部变化复杂，可能会导致需要定义很多具体建造者类来实现这种变化，导致系统变得很庞大，增加系统的理解难度和运行成本。</div></li><li><div>与工厂模式相比，采用生成器模式创建对象的客户，需要具备更多的领域知识。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、可能的改进</span></div><ol><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/builder/sample01">在Java中，使用XML指定使用哪个具体Builder类。</a></div></li><li><div>引入模板方法对建造者模式进行改进。</div></li><li><div>省略Director。有些情况下，可将Director和抽象建造者Builder合并，在Builder中，提供逐步构建复杂产品对象的Consctruct()方法，由于Builder类通常是抽象类，可以将construct()定义为静态方法。</div></li></ol><div><span style="font-weight: bold;">五、与其他设计模式的对比</span></div><div>1.建造者模式 vs. 工厂方法模式</div><ul><li><div>建造者模式最主要的功能是基本方法的调用顺序安排，也就是这些基本方法已经实现了（零件实现了，剩下的是装配零件），顺序不同产生的对象也不同。</div></li><li><div>工厂方法模式重点是创建，创建零件是它的主要职责，组装顺序不是它关心的。</div></li></ul><div>2.建造者模式 vs. 抽象工厂模式</div><ul><li><div>建造者模式返回一个完整的复杂产品，客户端通过制定具体建造者类型并指导Director类如何去生成对象，侧重于一步步构建一个复杂对象，然后将结果返回。（像一个汽车组装工厂）</div></li><li><div>抽象工厂返回一系列相关的产品，客户端通过选择具体工厂来生成所需对象。（像一个汽车配件厂）</div></li></ul><div><span style="font-weight: bold;">六、个人理解</span></div></div><ul><li><div><span style="font-weight: bold;">建造者模式的核心思路在于“流程”和“组装”两个词</span>。将产品视为由不同配件组装而成的，然后设定一套流程对产品进行组装，由“导演”类对流程进行控制，由具体的Builder类来实现不同的配件，相同的流程选择不同的具体Builder类，实现不同的产品，但流程本身（有几个步骤，都是什么）由抽象基类Builder控制。</div></li><li><div>建造者模式抽象的是过程，在抽象过程中，需要逐步完善整个流程，将通用的过程作为借口加入基类中。</div></li><li><div>建造者模式的使用不要小气，可以像书中所说构建一个系统，也可以应用到分布式系统中，应用于大型系统的组件创建中，可以有很广阔的扩展余地，值得好好考虑一下。</div></li></ul><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="19953"/>
<h1>1. 抽象工厂（AbstractFactory）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/3/29 10:55</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2020/12/2 6:05</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
<tr><td><b>来源：</b></td><td><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test"><i>https://github.com/PhycheLiu/DesignPattern_Study-Test</i></a></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div>一、基本描述</div><ul><li><div>提供一个接口以创建一系列相关或相互依赖的对象，而无需指定它们的具体类。</div></li><li><div>抽象工厂模式适合解决涉及到多个产品系列的问题。</div></li><li><div>抽象工厂模式是工厂方法模式的升级版本，在有多个业务品种、业务分类时，通过抽象工厂模式产生需要的对象是一种非常好的解决方式。</div></li><li><div>通过抽象工厂所提供的接口，可以创建产品的家族，利用这个接口书写代码，我们的代码将从实际工厂解耦，以便在不同上下文中实现各式各样的工厂，制造出各种不同的产品。例如：不同的区域、不同的操作系统、不同的外观及操作。</div></li><li><div>因为代码从实际的产品中解耦了，所以我们可以替换不同的工厂来取得不同的行为。</div></li><li><div>抽象工厂允许客户使用抽象的接口来创建一组相关的产品，而不需要知道（或关心）实际产出的具体产品是什么。这样一来，客户就从具体的产品中被解耦。</div></li></ul><div>二、图例</div><div style="margin-left: 40px;"><span style="font-size: unset; color: unset; font-family: unset;">最基本的抽象工厂模式类图如下图所示。</span></div><div style="text-align: center;"><img src="0.设计模式专题_files/Image [27].png" type="image/png" data-filename="Image.png" width="431"/></div><ul><li><div>AbstractFactory：抽象工厂为产品家族提供接口。抽象工厂定义了一个接口，所有的具体工厂都必须实现此接口，这个接口包含一组方法用来生产产品。</div></li><li><div>ConcreteFactory：从抽象工厂中派生出一些具体工厂，这些工厂产生相同的产品，但是产品的实现不同。具体工厂实现不同的产品家族。要创建一个产品，客户只要使用其中的一个工厂而完全不需实例化任何产品对象。</div></li><li><div>AbstractProdectA、ProductA1、AbstractProdectB、ProductB2：这就是产品家族，每个具体工厂都能够生产一整组的产品。</div></li><li><div>Client：客户的代码只需涉及抽象工厂，运行时将自动使用实际的工厂。</div></li></ul><div>三、优缺点</div><div><span style="font-weight: bold;">优点</span>：</div><ul><li><div>抽象工厂模式分离了具体的类。隔离具体类的生成，使客户不需要知道何时会创建，更换具体工厂很容易，只需要改变具体工厂实例，就可以在某种程度上改变整个软件系统的行为。（Client只使用AbstractFactory、AbstractProductA、AbstractProductB即可，无需知道具体使用的是哪个工厂，具体创建的是productA1还是productA2）</div></li><li><div>易于交换产品系列。改变一个应用的具体工厂变得很容易，一个抽象工厂创建了一个产品系列，改变工厂，就改变了产品系列。（在应用中，切换ConcreteFactory1和ConcreteFactory2很容易）</div></li><li><div>有利于产品的一致性。当一个产品系列的对象被设计成一起工作时，一个应用一次只能使用同一个系列中的对象。（如果确定了一个具体的工厂，如ConcreteFactory1，就唯一确定了一个产品族：productA1和productB1，绝对不会创建出productA2或productB2）</div></li></ul><div><span style="font-weight: bold;">缺点</span>：</div><ul><li><div>难以增加新种类的产品。（上图中，增加一个productC）</div></li></ul><div>四、可能的改进</div><ol><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Cplus/AbstractFactory/AbstractFactory_2">利用简单工厂对抽象工厂进行修改。</a></div></li><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/abstract_factory/sample01">在Java中，</a><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/abstract_factory/sample01" style="font-size: 16px; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; color: rgb(36, 41, 46); font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;; font-variant-caps: normal; font-variant-ligatures: normal;">使用XML指定使用哪个具体工厂类。</a></div></li></ol><div>五、个人理解</div><ul><li><div>抽象工厂的重点在工厂，也在前期对产品、产品系列的抽象<br/></div></li></ul></div><div><br/></div></span>
</div>
<hr>
<a name="20522"/>
<h1>3.工厂方法（FactoryMethod）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/4/12 7:28</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2020/11/30 8:10</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>定义一个用于创建对象的接口，让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。</div></li><li><div>工厂方法模式是一个工厂抽象接口和多个具体生成对象的工厂，增加功能时，不需要更改原有的工厂类，只要增加相应的具体工厂即可。</div></li><li><div>工厂方法模式只在一开始实例化具体工厂类的对象时会做出选择，一旦具体工厂类确定，后续所有的操作均无需修改。</div></li><li><div>又称为工厂模式、虚拟构造器模式、多态工厂模式。</div></li><li><div>所有工厂模式都用来封装对象的创建。工厂方法模式（ Factory Method Pattern）通过让子类决定该创建的对象是什么，来达到将对象创建的过程封装的目的。</div></li><li><div>工厂方法模式能够封装具体类型的实例化。</div></li><li><div>对于定义中提到的：工厂方法让子类决定要实例化的类是哪一个。所谓的“决定”，并不是指模式允许子类本身在运行时做决定，而是指在编写创建者类时，不需要知道实际创建的产品是哪一个。选择了使用哪个子类，自然就决定了实际创建的产品是什么。</div></li><li><div>工厂方法可以封装变化：可以将创建对象的代码封装起来。实例化具体类的代码，很可能在以后经常需要变化。而工厂方法模式可以封装实例化的行为。</div></li><li><div>工厂方法模式将创建对象的代码集中在一个对象或方法中，可以避免代码中的重复，并且更方便以后的维护。这也意味着客户在实例化对象时，只会依赖于接口，而不是具体类。这可以帮助针对接口编程，而不针对实现编程。这让代码更具有弹性，可以应对未来的扩展。</div></li><li><div>与简单工厂模式的区别：参数化的工厂方法看起来很像简单工厂。</div></li><ul><li><div>简单工厂把全部的事情在一个地方处理完了</div></li><li><div>工厂方法创建一个框架，让子类决定要如何实现</div></li></ul><li><div>工厂方法模式有两种形式</div></li><ul><li><div>参数化工厂方法，它可以根据传入的参数创建不同的对象</div></li><li><div>工厂经常只产生一种对象，不需要参数化</div></li></ul></ul><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div><img src="0.设计模式专题_files/Image [28].png" type="image/png" data-filename="Image.png" width="424"/></div><ul><li><div><span style="font-weight: bold;">Product</span>：工厂生产的产品。定义工厂方法锁创建的对象的接口，即对象的抽象类。负责定义产品的共性，实现对事物最抽象的定义。所有的产品必须实现这个共同的接口，这样一来，使用这些产品的类就可以引用这个接口，而不是具体类。</div></li><li><div><span style="font-weight: bold;">ConcreteProduct</span>：实现Product接口。被创建的具体的产品。</div></li><li><div><span style="font-weight: bold;">Creator</span>：这是抽象创建者类。它定义了一个抽象的工厂方法，让子类实现此方法制造产品。声明工厂方法，该方法返回一个Product类型的对象，Creator也可以定义一个工厂方法的缺省实现，它返回一个缺省的ConcreteProduct。即，Creator可以是抽象类（或接口），也可以是一个具体类，实现一个默认的工厂方法。</div><div>它实现了所有操作产品的方法，但不实现工厂方法。</div><div>创建者通常会包含依顿于抽象产品的代码，而这些抽象产品由子类制造。<span style="font-size: unset; color: unset; font-family: unset;">创建者不需要真的知道在</span>制造<span style="font-size: unset; color: unset; font-family: unset;">哪种具体产品。</span></div></li></ul><div style="margin-left: 40px;">工厂方法：</div><ul><ul><li><div>工厂方法用来处理对象的创建，并将这样的行为封装在子类中。这样，客户程序中关于超类的代码就和子类对象创建代码解耦了。</div></li><li><div>工厂方法可能需要参数（也可能不需要），来指定所要的产品。</div></li><li><div>工厂方法将客户和实际创建具体产品的代码分割开来。</div></li></ul><li><div><span style="font-weight: bold;">ConcreteCreator</span>：能够产生产品的类称为具体创建者。重定义工厂方法以返回一个ConcreteProduct实例。是具体的创建类，与ConcreteProduct一一对应。实际使用时，除了创建产品对象外，还可负责产品对象的初始化工作以及一些资源与环境配置工作。</div><div>当只有一个 ConcreteCreator 的时候，工厂方法模式依然很有用，因为它帮助我们将产品的“实现”从“使用”中解耦。如果增加产品或者改变产品的实现， Creator并不会受到影响。</div></li></ul><div>下图也是工厂方法模式的图例，可以更清晰的理解工厂方法中的“一一对应”的关系。</div><div><img src="0.设计模式专题_files/Image [29].png" type="image/png" data-filename="Image.png" width="558"/></div><ul><li><div>平行的类层级</div><div>产品类和创建者类层级是平行的。</div><div>因为它们都有抽象类，而抽象类都有许多具体的子类，每个子类都有自己特定的实现。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点：</span></div><ul><li><div>良好的封装性，代码结构清晰。（对产品的创建过程进行封装）</div></li><li><div>扩展性很好。符合“开闭原则”。</div></li><li><div>屏蔽产品类。（只需要关心产品的接口，在数据库开发中很好的应用了这一模式）</div></li><li><div>工厂方法模式是典型的解耦框架。基于工厂角色和产品角色的多态性设计是工厂方法模式的关键，可以让工厂自主确定创建何种产品。</div></li></ul><div><span style="font-weight: bold;">缺点：</span></div><ul><li><div>具体产品类与工厂类一一对应，新增类时，类的个数成对增加。</div></li><li><div>考虑可扩展性，需引入抽象层，在客户端代码中均使用抽象层进行操作，相对而言，增加了系统的抽象性和理解难度。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、可能的改进</span></div><ol><li><div><span style="font-size: 16px; letter-spacing: normal; orphans: 4; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;"><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/factory_method/section1" style="font-size: 16px; font-family: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-variant-caps: normal; font-variant-ligatures: normal;">使用模板和反射的方法，改进工厂方法模式。</a></span></div></li><li><div><span style="font-size: 16px; letter-spacing: normal; orphans: 4; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-variant-caps: normal; font-variant-ligatures: normal;"><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/factory_method/section3" style="font-size: 16px; font-family: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-variant-caps: normal; font-variant-ligatures: normal;">将工厂方法缩小为简单工厂模式。</a>也即所谓的简单工厂模式。</span></div></li><li><div><span style="color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;;">使用XML和反射机制确定使用哪个具体工厂类。</span></div></li></ol><div><span style="font-size: 14pt; font-weight: bold;">五、个人理解</span></div><ul><li><div>工厂方法模式是最常用、最基础的创建型设计模式，有很好的扩展性和封装性，在选择创建型模式的时候，应当首先想到工厂方法模式。</div></li><li><div>工厂方法模式最基本的架构是一个具体产品类对应一个具体的工厂类，但是有多个变种，甚至所谓的简单工厂模式也是工厂方法模式的一个变种。</div></li><li><div>工厂方法模式的主要思路是将创建对象的职责与知识集中起来。</div></li></ul><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="20697"/>
<h1>4. 原型（Prototype）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/4/18 14:52</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/11 1:00</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
<tr><td><b>来源：</b></td><td><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test"><i>https://github.com/PhycheLiu/DesignPattern_Study-Test</i></a></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div><span style="font-weight: bold;">用原型实例指定创建对象的种类，并且通过拷贝这些原型创建新的对象。</span></div></li><li><div>原型模式其实就是从一个对象再创建另外一个可定制的对象，而且不需知道任何创建的细节。</div></li><li><div>原型模式的实现与语言本身密切相关。</div></li><li><div>一般在初始化的信息不发生变化的情况下，原型模式是一个好的选择，这既可以隐藏对象创建的细节，又对性能有很大的提高</div></li><li><div><span style="font-weight: bold;">原型模式不用重新初始化对象，而是动态的获得对象运行时的状态。</span></div></li><li><div><span style="font-weight: bold;">原型模式在实现时有深克隆和浅克隆一说，需要根据情况进行选择。</span></div></li><li><div>原型模式很简单，所以应用的场景非常多。</div></li><li><div>原型模式的核心是一个clone方法，通过该方法进行对象拷贝，Java提供了一个Cloneable接口来表示这个对象是可拷贝的。但是这个clone方法实现的是浅拷贝，可以利用Object类的MemberwiseClone()或者序列化来实现深拷贝。</div></li><li><div>原型模式的工作原理：将一个原型对象传递给那个要发动创建的对象，要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。</div></li><li><div>原型模式的核心在于如何实现克隆方法。</div></li><li><div>原型模式的特点在于通过“复制”一个已经存在的实例来返回新的实例，而不是新建实例，被复制的实例就是所谓的“原型”，这个“原型”是可定制的。</div></li><li><div>原型模式多用于创建复杂的或者耗时的实例，因为这种情况下，复制一个已经存在的实例使程序运行更高效，或者创建值相等，只是命名不一样的同类数据。</div></li><li><div>当创建给定类的实例的过程很昂贵或者很复杂时，就使用原型模式。</div></li><li><div>原型模式允许你通过复制现有的实例来创建新的实例（在Java中，这通常意味着使用 clone() 方法，或者反序列化）。<span style="color: unset; font-family: unset; font-size: unset;">这个模式的重点在于，客户的代码在不知道要实例化何种特定类的情况下，可以制造出新的实例。</span></div></li><li><div>在一个复杂的类层次中，当系统必须从其中的许多类型创建新对象时，可以考虑原型。</div></li></ul><div><font style="font-size: 14pt;"><span style="font-size: 14pt; font-weight: bold;">二、图例</span></font></div><div><img src="0.设计模式专题_files/Image [30].png" type="image/png" data-filename="Image.png" width="437"/></div><ul><li><div>Prototype：声明一个克隆自身的接口；是声明克隆方法的接口，是所有具体原型类的公共父类，可以使抽象类也可以是接口，甚至可以是具体实现类；在这个类中， 最关键的是声明一个clone方法。</div></li><li><div>ConcretePrototype：实现一个克隆自身的操作；在实际应用时，这个类应先被实例化，然后才能实现克隆；实现在抽象原型类中声明的克隆方法，在克隆方法中返回自己的一个克隆对象。</div></li><li><div>Client：让一个原型克隆自身从而创建一个新的对象；由于客户类针对抽象原型类Prototype编程，因此用户可以根据需要选择具体原型类。</div></li></ul><div><font style="font-size: 14pt;"><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></font></div><div><span style="font-weight: bold;">优点：</span></div><ul><li><div>性能优良。比new一个对象性能好很多，特别是要在一个循环体内产生 大量的对象时。</div></li><li><div>逃避构造函数的约束。是优点，也是缺点，构造函数不会执行。</div></li><li><div>当创建新的对象实例较为复杂时，使用原型模式可以简化对象的创建过程。</div></li><li><div>扩展性较好。客户端可以针对抽象原型类进行编程，而将具体的原型类写在配置文件中，增加或减少具体原型类对原有系统没有影响。</div></li><li><div>原型模式提供了简化的创建结构。产品的复制是通过封装在原型类中的克隆方法实现的，无需专门的工厂类来创建产品。</div></li><li><div>向客户隐藏制造新实例的复杂性。</div></li><li><div>提供让客户能够产生未知类型对象的选项。</div></li><li><div>在某些环境下，复制对象比创建新对象更有效。</div></li></ul><div><span style="font-weight: bold;">缺点：</span></div><ul><li><div>逃避构造函数的约束。是优点也是缺点。</div></li><li><div>需要为每一个类配备一个克隆方法，当对已有的类进行改进时，要修改源代码。</div></li><li><div>对象的复制有时相当复杂。如：</div><div>在实现深克隆时要编写较复杂的代码，当对象之间存在多重嵌套引用时，为实现深克隆，每一层对象对应的类都必须支持深克隆，实现起来比较麻烦。</div></li></ul><div><span style="font-weight: bold; font-size: 14pt;">四、可能的改进</span></div><ol><li><div>使用克隆管理器对原型模式进行改进。（<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Cplus/Prototype/Prototype_1">C++版本</a>，<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/prototype/%E5%8E%9F%E5%9E%8B%E7%AE%A1%E7%90%86%E5%99%A8">Java版本</a>）</div></li><li><div><span style="font-size: 16px; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; color: rgb(36, 41, 46); font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;; font-variant-caps: normal; font-variant-ligatures: normal;"><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/prototype/%E7%9B%B8%E4%BC%BC%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%A4%8D%E5%88%B6" style="font-size: 16px; font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;; font-variant-caps: normal; font-variant-ligatures: normal;">相似对象的复制</a>。</span></div></li><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/prototype/sample02" style="color: rgb(36, 41, 46);">在Java中，使用序列化的方式实现深克隆。</a></div></li></ol><div><span style="font-weight: bold; font-size: 14pt;">五、个人理解</span></div><ul><li><div>原型模式可以参考“Ctrl+C”+“Ctrl+V”来理解。</div></li><li><div>原型模式很简单，其关键是一个clone方法，但是在具体实现这个clone方法的时候，有深拷贝与浅拷贝两种，很有讲究。</div></li><li><div>原型模式的实现与语言本身有很大的关系。</div></li><li><div>使用C++实现时，如果具体原型类中有成员变量，实现时必须在具体原型类中实现一个拷贝构造函数，但如果没有成员变量，拷贝构造函数可以不实现。</div></li></ul><div><span style="font-weight: bold; font-size: 14pt;">六、其他</span></div><div>      对Java中实现时的语句<span style="font-weight: bold;">super.clone()</span>的研究。</div><div>      在Java语言中实现clone方法时，一般都有一个核心的实现：super.clone()</div><div>      例如：</div><div>      <img src="0.设计模式专题_files/Image [31].png" type="image/png" data-filename="Image.png" width="359"/></div><div>      经过以下代码的验证：</div><div>        this.getclass().getSuperclass().getName();</div><div>      可以发现，<span style="font-weight: bold;">这里的super其实就是java.lang.Object，也就是所有Java类的基类Object类。所以这里的super.clone()其实就是调用Object类中实现的clone()方法。</span></div><div>      由此，利用Java的基础设施实现克隆方法：</div><ol><li><div>利用Java的Object类，实现克隆方法，必须实现标识接口Cloneable。</div></li><li><div>Object类提供了一个Clone方法，可以将一个Java对象复制一份，Java中可以直接使用Object提供的Clone方法来实现对象的克隆。</div></li><li><div>直接采用Object类的clone方法：1）在具体原型类中覆盖基类的Clone方法；2）在派生类的Clone中，调用super.clone()（实际上就是调用Object的Clone方法）；3）派生类要实现Cloneable接口；4）此时，<span style="font-weight: bold;">Object类相当于抽象原型类，所有实现了Cloneable接口的类相当于具体原型类。</span></div></li></ol><div><span style="font-weight: bold; font-size: 14pt;">七、待研究</span></div><ul><li><div>都在说原型模式使用克隆，比使用构造函数new一个对象性能有很大的提高，到底是否是这样？不同的语言是否表现一样？</div></li></ul><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="20916"/>
<h1>5.单例（Singleton）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/4/26 10:09</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/12 7:43</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><font style="font-size: 14pt;"><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></font></div><ul><li><div>保证一个类仅有一个实例，并提供一个访问它的全局访问点。</div></li><li><div>客户端不用考虑是否需要去实例化的问题，而把责任都给了应负责的类去处理。</div></li><li><div>单例模式的三个要点：1）单例类只有一个实例对象；2）单例对象必须由单例类自行创建；3）单例类对外提供一个访问该单例的全局访问点。</div></li><li><div>单例模式可以确保程序中使用的全局资源只有一份。单例模式常常被用来管理共享的资源，比如数据库连接或者线程池。</div></li></ul><div><font style="font-size: 14pt;"><span style="font-size: 14pt; font-weight: bold;">二、图例</span></font></div><div><img src="0.设计模式专题_files/Image [32].png" type="image/png" data-filename="Image.png" width="412"/></div><ul><li><div>Singleton：负责创建它自己的唯一实例。典型的Singleton类必须由三个成员：1）private权限的构造函数；2）private权限，static的Singleton类型的成员变量；3）public、static的外部访问接口Instance。</div></li></ul><div><font style="font-size: 14pt;"><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></font></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>可以保证唯一的实例。</div></li><li><div>对唯一实例的受控访问。（怎样访问，何时访问）</div></li><li><div>节约系统资源，对于频繁创建销毁的对象，可提高系统性能。</div></li><li><div>允许可变数目的实例。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>单例模式一般没有接口，扩展很困难，如果要扩展，除了修改代码基本没有其他途径。</div></li><li><div>单例模式对测试不利，在并发环境中，如果单例没有完成，无法进行测试，没有接口也不能使用mock方式虚拟一个对象。</div></li><li><div>单例与单一职责原则有冲突，一个类应该只实现一个逻辑，而不关心它是否是单例的，单例模式把“要单例”同业务逻辑融合在一个类中。（鸡蛋里挑骨头）</div></li><li><div>自动垃圾回收技术会销毁并回收长时间不利用的对象，会导致状态丢失。（鸡蛋里挑骨头）</div></li></ul><div><font style="font-size: 14pt;"><span style="font-size: 14pt; font-weight: bold;">四、可能的改进</span></font></div><ol><li><div>饿汉式单例实现 - 这种方法天然线程安全。（<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Cplus/Singleton/Singleton_6">C++版本</a>、<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/singleton/section3">Java版本</a>）</div></li><li><div>懒汉式单例实现。（<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Cplus/Singleton/Singleton_5">C++版本</a>、<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/singleton/section4">Java版本</a>）</div></li><li><div>使用双重检查锁定的线程安全版本的懒汉式单例实现。（<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Cplus/Singleton/Singleton_7">C++版本</a>、<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/singleton/section4">Java版本</a>）</div></li><li><div>使用IoDH技术实现的单例模式。（<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/singleton/sample01">Java版本</a>）</div></li><li><div>带有扩展子类的单例模式。（《设计模式》书中提到的改进）</div></li><li><div>指定有限数目对象的单例模式。（<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/singleton/section2">Java版本</a>）</div></li><li><div>通过静态内部类实现的单例模式。（<a href="https://time.geekbang.org/column/article/194035">Java版本</a>）</div></li></ol><div><font style="font-size: 14pt;"><span style="font-size: 14pt; font-weight: bold;">五、个人理解</span></font></div><ul><li><div>单例模式很简单，有很多变种，实现方式很多，应用非常广泛。但是要注意不要过度设计，滥用单例模式。</div></li><li><div>单例模式有最基本的饿汉和懒汉两种方式实现，饿汉一般不存在线程安全问题，懒汉一般需要采用双重检查锁定技术来确保线程安全。</div></li><li><div>如果应用程序大量的使用了单例模式，那么可能需要再好好的检查设计，因为通常适合使用单例模式的机会不多。</div></li><li><div>单例模式 vs. 全局变量</div><div>单例模式的终极目的是：确保类只有一个实例并提供全局访问。</div><div>全局变量提供全局访问，但是不能确保只有一个实例。</div><div>全局变量也会变相鼓励开发人员，用许多全局变量指向许多小对象来造成命名空间的污染。</div></li></ul><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="21319"/>
<h1>6. 适配器（Adapter）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/5/10 10:11</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/9 8:02</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。</div></li><li><div>适配器模式主要应用于希望复用一些现存的类，但接口又与复用环境要求不一致的情况。</div></li><li><div>适配器模式不应滥用：只有在双方都不太容易修改的时候再使用适配器，否则应优先考虑通过重构统一接口。</div></li><li><div>事后适配不如事中重构，事中重构不如事前预防。</div></li><li><div>适配器模式是对于意外的补救模式，解决接口不相容问题。</div></li><li><div>适配器模式又称为变压器模式，包装模式（Wrapper）。</div></li><li><div>适配器并不完美，客户必须小心潜在的异常，但只要客户够小心，而且适配器的文档能做出说明，这也算是一个合理的解决方案。</div></li><li><div>客户使用适配器的过程如下：</div></li><ul><li><div>客户通过目标接口调用适配器的方法对适配器发出请求</div></li><li><div>适配器使用被适配者接口把请求转换成被适配者的一个或多个调用接口</div></li><li><div>客户接收到调用的结果，但并未察觉这一切是适配器在起转换作用</div></li></ul></ul><div style="margin-left: 40px;">客户和被适配者是解耦的，一个不知道另一个。</div><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><ol><li><div>类适配器：</div></li></ol><div><img src="0.设计模式专题_files/Image [33].png" type="image/png" data-filename="Image.png" width="573"/></div><ol start="2"><li><div>对象适配器（更通用）：</div></li></ol><div><img src="0.设计模式专题_files/Image [34].png" type="image/png" data-filename="Image.png" width="561"/></div><ul><li><div>Client：与符合Target接口的对象协同。调用Target提供的接口。</div></li><li><div>Target：定义Client使用的与特定领域相关的接口。要使用的接口，Adaptee的接口要向Target的接口靠拢。</div></li><li><div>Adapter：对Adaptee的接口与Target接口进行适配，适配器。</div><div>一般来说，适配器实现了目标接口，并持有被适配者的实例。</div></li><li><div>Adaptee：定义一个已经存在的接口，这个接口需要适配。需要适配接口的类，已经存在的类，其接口要向Target提供的接口靠拢。</div></li></ul><div>对象适配器 Vs. 类适配器</div><ul><li><div>对象适配器</div></li><ul><li><div>使用组合，不仅可以适配某个类，也可以适配该类的任何对象</div></li><li><div>只需要写一些代码，将工作委托给被适配者进行，这样更加有弹性</div></li></ul><li><div>类适配器</div></li><ul><li><div>不需要重新实现整个被适配者，必要的时候，可以覆盖被适配者的行为</div></li><li><div>需要多继承的语言支持</div></li></ul></ul><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ol><li><div>共同的优点：</div></li></ol><ul><li><div>将目标类与适配者类解耦，通过引入一个适配器类来重用现有的适配者类，无需修改原有的结构。</div></li><li><div>增加了类的透明性和复用性，将具体的业务实现过程封装在适配者类中，对于客户端而言是透明的，而且提高了适配者的复用性，同一个适配这类可以在多个不同的系统中复用。</div></li><li><div>灵活性和扩展性非常好，可以很方便的更换适配器，也可以在不修改原有代码的基础上增加新的适配器类，完全符合“开闭原则”。</div></li></ul><ol start="2"><li><div>类适配器的优点：</div></li></ol><ul><li><div>由于适配器类是适配者类的子类，因此可以在适配器类中置换一些适配者的方法（子类覆盖父类的方法），使得适配器的灵活性更强。</div></li></ul><ol start="3"><li><div>对象适配器的优点：</div></li></ol><ul><li><div>一个对象适配器（Adapter）可以把多个适配者（Adaptee）适配到同一个目标（Target）。</div></li><li><div>可以适配一个适配者的子类，由于适配器和适配者之间是关联关系，根据“里氏代换原则”，适配者的子类也可以通过该适配器进行适配。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ol><li><div>类适配器的缺点：</div></li></ol><ul><li><div>对Java、C#等不支持多重类继承的语言，一次最多只能适配一个适配者类，不能同时适配多个适配者。</div></li><li><div>适配者类不能为最终类，如在Java中不能为final类，在C#中不能为sealed类。</div></li><li><div>在Java、C#等语言中，类适配器模式中的目标抽象类只能为接口，不能为类，其使用有一定的局限。</div></li></ul><ol start="2"><li><div>对象适配器的缺点：</div></li></ol><ul><li><div>在适配器中置换适配者的某些方法比较麻烦。（不能覆盖适配者类中提供的某些方法）</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、可能的改进</span></div><ol><li><div>双向适配器：Adaptee要使用Target中的方法，Target也要使用Adaptee中的方法。（很少使用）</div></li><li><div>缺省适配器（Default Adapter Pattern）：适用于不想使用一个接口中所有方法的情况，又称为单接口适配器模式。</div></li><li><div><span style="color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;;">Java语言中使用XML和反射机制确定使用哪个适配器。（</span><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/adapter/sample01" style="font-family: &quot;Open Sans&quot;;">类适配器实现</a><span style="color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;;">，对象适配器实现）</span></div></li></ol><div><span style="font-size: 14pt; font-weight: bold;">五、个人理解</span></div><ul><li><div>适配器只负责接口之间的转换，一般与具体的实现无关。</div></li><li><div>适配器有类适配器和对象适配器的区别，一般对象适配器应用更广泛。（对象适配器对于覆盖Adaptee中的实现并不能很好的处理，但除此之外，均应使用对象适配器）</div></li><li><div>适配器模式比较简单，但是归根结底是一个补救措施，在设计和实现阶段不应使用，只有在无法拿到源码或修改代码代价巨大的情况下才考虑。</div></li></ul><div><font style="font-size: 14pt;"><span style="font-size: 14pt; font-weight: bold;">六、其他</span></font></div><div>      在C++的类适配器的实现中，Adapter要以public的方式继承Target，以private的方式继承Adaptee，这是因为：</div><ul><li><div>用户要使用Target，所以Adapter要体现“is-a” Target的关系。</div></li><li><div>Adapter和Adaptee是一种“is-implemented-in-terms-of（根据某物实现出）”的关系，根本上，使用的并不是Adaptee的接口，而是Adaptee的实现，所以以private继承时合理的。（当然也可以public的方式继承）</div></li></ul><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="21572"/>
<h1>7.桥接（Bridge）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/5/17 17:08</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/10 23:30</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>桥接模式将抽象部分与它的实现部分分离，使它们可以独立的变化。</div></li><li><div>桥接模式的核心意图是把这些实现独立出来，让它们各自的变化，这就使得每种实现的变化不会影响其他实现，从而达到应对变化的目的。</div></li><li><div>抽象与实现分离再理解：实现系统可能有多种角度分类，每一种分类都有可能变化，那么就把这种多角度分离出来让它们独立变化，减少它们之间的耦合。</div></li><li><div>桥接模式的重点是在“解耦”上。</div></li><li><div>桥接模式是一个非常简单的模式，它只使用了类间的聚合关系，继承，覆写等常用功能，但它却提供了一个非常清晰，稳定的架构。</div></li><li><div>使用桥接模式时主要考虑如何拆分抽象与实现，并不是一涉及到继承就要考虑使用该模式。</div></li><li><div>桥接模式的意图还是对变化的封装，尽量把可能变化的因素封装到最细、最小的逻辑单元中，避免风险扩散。</div></li><li><div>对于比较明确不发生变化的，通过继承实现，不能确定是否发生变化的，则认为会发生变化，通过桥接模式解决。</div></li><li><div>如果软件系统中某个类存在两个独立变化的维度，通过桥接模式可以将这两个维度分离出来，使两者可以独立扩展，让系统更加符合“单一职责原则”。</div></li><li><div>桥接模式用一种巧妙的方式处理多层继承存在的问题，用抽象关联取代了传统多层继承，将类之间的静态继承关系转换为动态的对象组合关系。</div></li><li><div>Adapter模式永磊帮助无关的类协同工作，它通常在设计模式完成后才会被使用。但Bridge模式则是在系统开始时就被使用，它使得抽象接口和实现部分可以独立进行改变。</div></li><li><div>使用桥接模式不只改变你的实现，也改变你的抽象。</div></li><li><div>适合使用在需要跨越多个平台的图形和窗口系统上。当需要使用不同的方式改变接口和实现时，你会发现桥接模式很好用。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div><img src="0.设计模式专题_files/Image [35].png" type="image/png" data-filename="Image.png" width="671"/></div><ul><li><div>Abstraction：定义抽象类的接口，维护一个指向Implementor的指针，是所谓的“抽象”。</div></li><li><div>RefinedAbstraction：扩充由Abstraction定义的接口。是“被提炼的抽象”。</div></li><li><div>Implementor：定义实现类的接口，该接口不一定与Abstraction的接口完全一致，事实上这两个接口可以完全不同。通常，Implementor接口仅提炼基本操作，而Abstraction则定义了基于这些基本操作的较高层次的操作。是对所谓的“实现”的抽象。</div></li><li><div>ConcreteImplementor：实现Implementor接口并定义它的具体实现。是对所谓的“实现”的具体实现。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>将抽象与实现分离。这是桥接模式的主要特点，完全是为了解决继承的缺点提出的设计模式。</div></li><li><div>优秀的扩充能力。</div></li><li><div>实现细节对客户透明。用户不用关心细节的实现，它已经由抽象层通过聚合关系完成了封装。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>会增加系统的理解与设计难度，由于关联关系建立在抽象层，要求开发者一开始就针对抽象层进行抽象与编程。</div></li><li><div>要求正确识别出系统中两个独立变化的维度，使用范围有一定的局限性，如何正确识别两个独立变化的维度也需要一定的经验累积。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、可能的改进</span></div><ol><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/bridge/sample01" style="font-family: &quot;Open Sans&quot;;">Java语言中使用XML和反射机制动态确定使用哪个抽象与实现。</a></div></li></ol><div><span style="font-size: 14pt; font-weight: bold;">五、个人理解</span></div><ul><li><div>桥接模式比较简单，但非常有用，应用非常广泛。</div></li><li><div>桥接模式在设计阶段就需要考虑。</div></li><li><div>对于桥接模式的应用实际上有两种：1）将抽象与实现解耦，或将两个独立变化的维度解耦。2）使用桥接在多个对象间共享一些实现。</div></li></ul><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="21906"/>
<h1>8.组合（Composite）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/5/24 16:27</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/24 5:46</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>组合模式可以将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。</div></li><li><div>组合模式描述了如何使用递归组合，使得用户不必对这些类进行区别。</div></li><li><div>组合模式也叫合成模式，有时又叫部分-整体模式，主要用来描述部分与整体的关系。</div></li><li><div>组合模式有两种不同的实现：透明模式实现与安全模式实现。</div></li><li><div>组合模式的关键是定义了一个抽象构件类，它既可以代表叶子，也可以代表容器，而客户端针对抽象构建类进行编程，无需知道它到底是叶子还是树枝，可以对其进行统一处理。同时，树枝对象与抽象构建类之间建立一个聚合关联关系，在容器对象中既可以包含叶子，也可以包含树枝，以此实现递归组合，形成一个树形结构。</div></li><li><div>组合模式让我们能用树形方式创建对象的结构，树里面包含了组合以及个别的对象。</div></li><li><div>使用组合结构，我们能把相同的操作应用在组合和个别对象上。换句话说，在大多数情况下，我们可以忽略对象组合和个别对象之间的差别。</div></li><li><div>组合模式是一个很典型的折衷案例，以单一责任设计原则换取透明性。</div><div>也就是说，一个元素究竟是组合节点还是叶节点，对于客户是透明的。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div><img src="0.设计模式专题_files/Image [36].png" type="image/png" data-filename="Image.png"/></div><ul><li><div>Component：为组合中的对象声明接口。在适当的情况下，实现所有类共有接口的缺省行为。声明一个接口用于访问和管理Component的子组件。（可选）在递归结构中定义一个接口，用于访问一个父部件，并在合适的情况下实现它。实际上是提供给用户使用的统一接口。</div><div>为叶节点和组合节点提供一个共同的接口。</div></li><li><div>Leaf：在组合中表示叶结点对象，叶结点没有子结点。在组合中定义图元对象的行为。</div></li><li><div>Composite：定义有子部件的那些部件的行为。存储子部件。在Component接口中实现与子部件有关的操作。是组合模式中的树枝结点。</div></li><li><div>Client：通过Component接口操纵组合部件的对象。客户端只针对Component接口编程，无需关心具体子类是Composite树枝还是Leaf叶子。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>可以清楚的定义分层次的复杂对象，表示对象的全部或者部分层次，它让客户端忽略了层次的差异，方便对整个层次结构进行控制。</div></li><li><div>客户端可以一致的使用一个组合结构或其中单个对象，不必关心是处理的单个结构还是整个组合结构。</div></li><li><div>增加新的树枝构件和叶子构件很方便，无须对现有类库修改，符合“开闭原则”。</div></li><li><div>为树形结构的面向对象提供了一种灵活的解决方案。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>设计较为复杂，客户端要理清类之间的层次关系。</div></li><li><div>不容易用继承的方法来增加构件新功能。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、可能的改进</span></div><ol><li><div> 组合模式似乎没有特别的变种，但是有“安全模式”和“透明模式”两种方式的实现：</div></li></ol><ul><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/composite/section4" shape="rect">安全模式</a></div></li></ul><div style="padding-left: 30px;">在Component接口中不去声明Add和Remove方法，子类的Leaf不需要实现这两个方法，而是在Composite声明所有用来管理子类对象的方法。</div><div style="padding-left: 30px;">-- 优点：安全，因为不向叶子对象提供这些管理成员对象的方法。</div><div style="padding-left: 30px;">-- 缺点：不够透明，叶子构件与容器构件具有不同的方法，在抽象构件类中未定义管理成员对象的方法，因此客户端不能完全针对抽象接口编程，必须有区别的对待叶子构件和容器构件。</div><ul><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/composite/section5" shape="rect">透明模式</a></div></li></ul><div style="padding-left: 30px;">在Component中声明所有用来管理子对象的方法，其中包括Add、Remove等。这样，实现了Component接口的所有子类都具备了Add和Remove方法（包括Leaf）。是组合模式的标准形式。</div><div style="padding-left: 30px;">-- 优点：确保所有的构件类都具有相同的接口，客户端可以一致的对待所有对象，使用方便。</div><div style="padding-left: 30px;">-- 缺点：不够安全，因为叶子结点不可能有下一个层次的对象，因此为其提供Add和Remove等方法没有意义，这在编译阶段不会出错，但如果处理不当（如没有提供相应错误处理代码），在运行阶段，叶子结点调用这些方法会出现错误。</div><div style="padding-left: 30px;">2. 有时候，如果组合结构很复杂，或者遍历的代价太高，那么实现组合节点的缓存就很有帮助。比如，如果要不断的遍历一个组合，而且它的每一个子节点都需要进行某些计算，那就应该使用缓存来临时保存结果，省去遍历的开支！</div><div><span style="font-size: 14pt; font-weight: bold;">五、个人理解</span></div><ul><li><div>在设计当中，只要发现有树状的结构，就要想到可以使用组合模式。</div></li><li><div>个人还是觉得透明模式比安全模式更好，最多小心一些，在透明模式的实现时，完善Leaf结点的管理方法中的错误处理。</div></li></ul><div><br clear="none"/></div></div><div><br clear="none"/></div></span>
</div>
<hr>
<a name="22022"/>
<h1>9.装饰（Decorator）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/5/31 11:22</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2020/11/10 8:09</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>动态的给一个对象添加一些额外的职责。就增加功能来说，Decorator模式相比生成子类更为灵活。</div></li><li><div>也叫包装器模式（Wrapper）。</div></li><li><div>装饰者和被装饰对象有相同的超类型。</div></li><li><div>可以用一个或多个装饰者包装一个对象。</div><div>既然装饰者和被装饰对象有相同的超类型，所以在任何需要原始对象（被包装的）的场合，可以用装饰过的对象代替它。</div></li><li><div>装饰者可以在所委托被装饰者的行为之前与/或之后，加上自己的行为，以达到特定的目的。</div></li><li><div>对象可以在任何时候被装饰，所以可以在运行时动态的、不限量的用你喜欢的装饰者来装饰对象。</div></li><li><div>装饰与它所装饰的组件接口一致，因此它对使用该组件的客户<span style="font-weight: bold;">透明</span>，它将客户请求<span style="font-weight: bold;">转发</span>给该组件，并且可能在转发前后执行一些<span style="font-weight: bold;">额外的动作</span>。透明性使得客户可以递归的嵌套多个装饰，从而可以添加任意多的功能。</div></li><li><div>如果将代码针对特定种类的具体组件，做一些特殊的事，即代码依赖于特定的具体组件类型，那么装饰者就会导致程序出问题，只有在针对抽象组件类型编程时，才不会因为装饰者而受到影响。但是，如果的确针对特定的具体组件编程，就应该重新思考应用架构，以及装饰者是否适合。</div></li><li><div>能够在不修改任何底层代码的情况下，给你的（或别人的）对象赋予新的职责。</div></li><li><div>在装饰模式中，客户通常不会感觉到装饰过的组件与未装饰组件之间的差异，也不会与装饰产生任何依赖关系。</div></li><li><div>装饰模式可以将所需的功能按照正确的顺序串联起来进行控制。</div></li><li><div>装饰模式将装饰对象的实现和如何使用这个对象分离，每个装饰对象之关心自己的功能，不需要关心如何被添加到对象链中。</div></li><li><div>装饰模式是为已有功能动态的添加更多的功能的一种方式。</div></li><li><div>装饰者可以在被装饰者的行为前面与/或后面加上自己的行为，甚至将被装饰者的行为整个取代掉，而达到特定的目的。</div></li><li><div>使用装饰者模式，必须管理更多的对象，所以犯下编码错误的机会会增加。所以，装饰者可以使用其他类似于工厂或生成器这样的模式创建，此时，它们会“封装得很好”，所以不会有这种问題。</div></li><li><div>原始方法和装饰方法的执行顺序在具体的装饰类中是固定的（即，在 ConcreteDecorator 类中的 Operation，AddedBehavior 和 ConcreteComponent-&gt;Operation的执行顺序是固定的），可以通过方法重载实现多种执行顺序。</div></li><li><div>有书上说装饰模式分为透明装饰模式和半透明装饰模式，但个人认为半透明装饰模式偏离了装饰模式的本意，不应提倡使用。（有关半透明装饰模式，可以直接看<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/decorator/sample01">代码</a>）。</div></li><li><div>装饰模式主要解决：过度使用继承来扩展对象的功能。</div></li><li><div>真实世界的装饰者：Java I/O</div></li><ul><li><div>java.io包内的类太多了，其中很多类都是装饰者。</div></li><li><div>人们第一次接触到Java I/O库时，往往无法轻易地理解它。但是如果他们能认识到这些类都是用来包装 InputStream 的，一切都会变得简单多了。</div></li></ul></ul><div>               <img src="0.设计模式专题_files/Image [37].png" type="image/png" data-filename="Image.png" width="462"/></div><div><br/></div><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="0.设计模式专题_files/Image [38].png" type="image/png" data-filename="Image.png" width="603"/></div><ul><li><div>Component：定义一个接口对象，可以给这些对象动态的添加职责。最基本、最核心、最原始的接口或抽象类。</div></li><li><div>ConcreteComponent：定义一个对象，可以给这个对象添加一些职责。需要装饰的对象，实际上可能会有很多个 ConcreteComponent。</div></li><li><div>Decorator：维持一个指向 Component 对象的指针，并定义有一个与 Component 接口一致的接口。在其 Operation 当中，需要将操作委托给 ConcreteComponent 来实现（调用 ConcreteComponent 中的 Operation 操作）。实际上是“装饰抽象类”，继承了 Component，从外类来扩展 Component 的功能，但对 Component 来说，无需知道 Decorator 的存在。</div></li><li><div>ConcreteDecorator：向组件添加职责。给 ConcreteComponent 添加职责，将最核心、最原始、最基本的东西装饰成其他东西。</div></li><li><div>Decorator 将请求转发给它的 Component 对象，并有可能在转发请求前后执行一些附加的动作。（用通俗的话说：Decorator 类的 Operation 将操作转发给 ConcreteComponent 的 Operation，转发的过程中可以添加自己特有的操作。）</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>装饰类和被装饰类可以独立发展，避免耦合。把类中的装饰功能从类中去除，简化原有的类，有效的把类的核心职责和装饰功能区分开了而且可以去除相关类中重复的装饰逻辑。</div></li><li><div>装饰模式是继承关系的一个替代方案。但是装饰模式比继承更有灵活性，不会导致类的个数急剧增加。</div></li><li><div>装饰模式可以动态的扩展一个实现类的功能。</div></li><li><div>可以对一个对象进行多次装饰，通过使用不同的具体装饰类以及这些装饰类的排列组合，可以创造出很多不同行为的组合，得到更多功能对象。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>装饰类比较复杂。装饰模式提供了一种比继承更加灵活的解决方案，但是同时也意味着比继承更加容易出错，排错更困难，对于多次装饰的对象，调试时寻找错误可能需要更多级的排查，较为繁琐。</div></li><li><div>使用装饰类进行系统设计时，将会产生很多小对象，这些对象的区别在于它们之间相互连接的方式有所不同，而不是它们的类或者属性值有所不同，大量小对象会影响系统资源，导致系统运行效率下降。</div><div>小对象数量实在太多，可能会造成使用此AP程序员的困扰。但是，了解了装饰者的工作原理，以后当使用别人的大量装饰的API时，就可以很容易地辨别出他们的装饰者类是如何组织的，以方便用包装方式取得想要的行为。但如果使用过度，会让程序变得很复杂。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、可能的改进</span></div><ul><li><div>如果只有一个 ConcreteComponent 类，则可以将 Component 省略，使用 ConcreteComponent 可以替代 Component 抽象接口，此时，Decorator 是 ComcreteComponent 的一个子类。</div></li><li><div>如果只有一个 ConcreteDecorator，则没有必要建立一个单独的 Decorator 类，而可以将 Decorator 和 ConcreteComponent 的责任合并成一个类。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">五、其他讨论</span></div><div><span style="font-size: 12pt;">   </span> <span style="font-size: 10pt;"> 1. 有关设计的讨论</span></div><div><span style="font-size: 10pt;">      一般设计下，当系统需要新功能的时候，是向旧的类中添加新的代码，这些新加的代码通常装饰了原有类的核心职责或主要行为，问题在于，它们在主类中加入了新的字段，新的方法和新的逻辑，从而增加了主类的复杂度，而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。</span></div><div><span style="font-size: 10pt;">      装饰模式针对上述问题给出解决方案，它把每个要装饰的功能放在单独的类中，并让这个类包装它所要装饰的对象，因此，当需要执行特殊行为时，客户代码就可以在运行时根据需要有选择的、按顺序的使用装饰功能包装对象了。</span></div><div><span style="font-size: 10pt;">      在面向对象的设计中，如果超过两层继承，就应该想想是不是设计出问题了，继承的层次越多，以后的维护成本越多。</span></div><ol start="2"><li><div>对装饰模式的更深入的理解</div></li></ol><ul><li><div>尽量保持装饰类的接口与被装饰类的接口相同，客户端可以一致对待装饰前和装饰后的对象。</div></li><li><div>尽量保持具体构建类 ConcreteComponent 是一个“轻”类，即，不要把太多的行为放在具体构建类中，可以通过装饰类对其扩展。</div></li><li><div>Decorator 类在接口上表现为 is-a Component 的继承关系，即 Decorator 类继承了 Component 类所具有的接口，但在实现上却又表现为 has-a Component 的组合关系，即 Decorator 类又使用了另外一个 Component 类。我们可以使用一个或多个 Decorator 对象来“装饰”一个 Component 对象，且装饰后对象仍然是一个 Component 对象。</div></li><li><div>Decorator 模式并非解决“多子类衍生的多继承”问题，Decorator 模式应用的要点在于解决“主题类在多个方向上的扩展功能”——是为“装饰”的含义。Decorator 是在运行时对功能进行组合。</div></li></ul><ol start="3"><li><div>对于合成复用原则的支持</div></li></ol><ul><li><div>Decorator 继承于 Component 类，看似并没有完全的用组合替代继承。</div></li><li><div>这么做的重点在于，装饰者和被装饰者必须是一样的类型，也就是有共同的超类，这是相当关键的地方。在这里，利用继承达到“类型匹配”，而不是利用继承获得“行为”。</div></li><li><div>将 Decorator 同 ConcreteComponent 组合时，就是在加入新的行为，所得到的新行为，并不是继承自超类，而是由组合对象得来的。</div></li><li><div>总结一下，装饰者模式当中有继承，但 Decorator 继承 Component 抽象类，是为了有正确的类型，而不是继承它的行为。行为来自装饰者和基础组件，或与其他装饰者之间的组合关系。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">六、个人理解</span></div><ul><li><div>装饰模式应用很广泛，会非常有用，是继承的很好的替代。</div></li><li><div>可以无限装饰同一个对象，可以动态的给对象增加功能，非常有用。</div></li><li><div>装饰模式致力于将装饰功能从主类中区分开来，更多针对多余的、辅助的功能，将这些功能从主类中移除，单独成为装饰类（ConcreteDecorator），这样保证了主类的简单、纯洁，也保证了装饰功能的动态可扩展。</div></li><li><div>在实际应用中，应当尽量减少对 ConcreteComponent 对象使用装饰类的数量，以便降低系统的复杂度和维护的难度。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div></div><div><br/></div></span>
</div>
<hr>
<a name="22193"/>
<h1>10.外观（Facade）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/6/7 14:18</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/11 7:49</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
<tr><td><b>来源：</b></td><td><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test"><i>https://github.com/PhycheLiu/DesignPattern_Study-Test</i></a></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>为系统中的一组接口提供一个一致的界面，Facade 模式定义了一个高层接口，这个接口使得这一子系统更加容易使用。</div></li><li><div>将一个系统划分成若干个子系统有利于降低系统的复杂性，一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小。</div></li><li><div>外观模式之所以这么称呼，是因为它将一个或数个类的复杂的一切都隐藏在背后，只显露出一个干净美好的外观。</div></li><li><div>外观对象为子系统中较一般的设施提供了一个单一而简单的界面。</div></li><li><div>外观模式又叫门面模式。</div></li><li><div>外观模式的主要目的在于降低系统的复杂度。</div></li><li><div>引入外观模式之后，增加新的子系统或移除子系统都非常方便，客户类无需修改（或只需极少修改），只需要在外观类中增加或移除对子系统的引用即可。</div></li><li><div>外观模式是一个使用频率异常高的设计模式。</div></li><li><div>外观模式并不给系统增加任何新的功能，仅仅是简化调用接口。</div></li><li><div>几乎所有的软件中均可以看到外观模式的影子：1）B/S中的首页或导航页面；2）C/S中的菜单或工具栏。</div></li><li><div>所有涉及到与多个业务对象交互的场景都可以考虑使用到外观模式进行重构。</div></li><li><div><font style="font-size: 10pt;"><span style="box-sizing: border-box; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; font-size: 10pt; color: rgb(36, 41, 46); font-variant-caps: normal; font-variant-ligatures: normal;">Facade不应该参与子系统内的业务逻辑</span><span style="letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; font-size: 10pt; color: rgb(36, 41, 46); font-variant-caps: normal; font-variant-ligatures: normal;">。Facade只是一个接口，不应该有业务逻辑。</span></font></div></li><li><div><font color="#24292E">有了外观模式，通过实现一个提供更合理的接口的外观类，可以将一个复杂的子系统变得容易使用。如果需要复杂子系统的强大威力，还是可以使用原来的复杂的接口，但如果需要的是一个方便使用的接口，那就使用外观。</font></div></li><li><div><font color="#24292E">外观只是提供更直接的操作，并没有将原来的子系统阻隔起来，如果需要子系统类的更高层功能，还是可以使用原来的子系统。</font></div></li><li><div><font color="#24292E">外观模式提供简化接口的同时，依然将系统完整的功能暴露出来，以提供需要的人使用。</font></div></li><li><div>外观可以附加“聪明的”功能，让使用子系统更方便。</div></li><li><div>可以为一个子系统创建许多个外观。</div></li><li><div>外观不只是简化了接口，也将客户从组件的子系统中解耦。</div></li><li><div>外观的意图是要提供一个简单的接口，好让一个子系统更容易使用。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="0.设计模式专题_files/Image [39].png" type="image/png" data-filename="Image.png" width="487"/></div><ul><li><div>Facade：知道哪些子系统类负责处理请求。将客户的请求代理给适当的子系统对象。Facade会将所有的从客户端发来的请求委派到相应的子系统去，即，Facade没有实际的业务逻辑，只是一个委托类。</div></li><li><div>子系统类：实现子系统的功能。处理由Facade对象指派的任务。没有Facade的任何相关信息，即没有指向Facade的指针。可以同时又一个或多个子系统，每个子系统都是一个类的集合，子系统不知道Facade的存在，对于子系统，Facade只是另外一个客户端。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>外观模式对客户屏蔽子系统组件，因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。</div></li><li><div>外观模式实现了子系统和客户之间的松耦合关系，而子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。Facade 模式有助于建立层次结构系统，也有助于对对象之间的依赖关系分层。Facade 模式可以消除复杂的循环依赖关系。</div></li><li><div>如果应用需要，外观模式并不限制它们使用子系统类，因此你可以在系统易用性和通用性之间加以选择。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>不符合“开闭原则”：对修改关闭，对扩展开放。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、可能的改进</span></div><ul><li><div>可以引入一个抽象外观类。客户端针对抽象外观类进行编程，对于新的业务需求，无需修改原有外观类，只需对应增加一个新的外观类，在运行时具体确定使用哪个具体外观类。</div></li><li><div><font style="font-size: 10pt;"><span style="box-sizing: border-box; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;"><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/facade/section5" style="font-size: 10pt; font-variant-caps: normal; font-variant-ligatures: normal;">一个子系统可以有多个Facade</a></span><span style="letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; font-size: 10pt; color: rgb(36, 41, 46); font-variant-caps: normal; font-variant-ligatures: normal;">。当：1）Facade已经大到难以忍受的程度，或者2）子系统希望提供不同的访问路径，的时候，可以建立多个Facade以提供不同的高层模块来访问。</span></font></div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">五、个人理解</span></div><ul><li><div>Facade 在分割分层系统这方面非常像一个网关。</div></li><li><div>有一些书上说客户端只能与Facade通信，这个说法是有问题的，原版的设计模式明确指出，一般用户可以与Facade通信，但是也可以直接同子系统中的某个类通信。</div></li><li><div>Facade特别简单，设置没有一个类图，只有一个示意图，但也正因为如此，各种实现均可以说自己是Facade，个人认为，Facade归根结底是一个接口，方便Client使用。</div></li><li><div>个人认为Facade不应参与业务逻辑，这并不是Facade模式本身的要求，一个参与业务逻辑的Facade也是Facade，但是参与业务逻辑之后，如果要修改Facade不太方便，另外此Facade就无法替换了，为后期的维护带来不便。</div></li><li><div>装饰者、适配器、外观</div><div>装饰者不改变接口，但加入责任</div><div>适配器将一个接口转成另一个接口</div><div>外观让接口更简单</div><div>外观和适配器可以包装许多类，但是外观的意图是简化接口，而适配器的意图是将接口转换成不同的接口。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="22995"/>
<h1>12.代理（Proxy）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/6/28 16:06</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/2/11 16:48</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>为其他对象提供一种代理以控制对这个对象的访问。</div></li><li><div>代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。</div><div>使用代理模式创建代表对象，让代表对象控制某对象的访问，被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。</div></li><ul><li><div>代理可以描述成另一个对象的代表</div></li><li><div>代理之所以需要控制访问，是因为客户不知道如何和远程对象沟通。代理模式有许多变体，而这些变体几乎都与访问控制的做法有关。</div></li><ul><li><div>远程代理控制访问远程对象</div></li><li><div>虚拟代理控制访问创建开销大的资源</div></li><li><div>保护代理就权限控制对资源的访问</div></li></ul></ul><li><div>在需要用比较通用和复杂的对象指针代替简单的指针的时候，使用Proxy模式。（是指用对象代替简单的指针，对象起到指针的作用。）</div></li><li><div>代理模式其实就是在访问对象时引入一定程度的间接性，因为这种间接性，可以附加多种用途。</div></li><li><div>代理其实就是真实对象的代表。</div></li><li><div>代理模式是一个使用率非常高的设计模式。代理的应用非常广泛，大到一个系统框架、企业平台，小到代码片段、事务处理，用途很多。</div></li><li><div>代理模式也叫做委托模式，是一项基本设计技巧，许多其他的模式，如状态模式，策略模式，访问者模式本质上是在更特殊的场合采用了委托模式。</div></li><li><div>一个代理类可以代理多个被委托者或被代理者，因此一个代理类具体代理哪个真实主题角色，是由场景类决定的。</div></li><li><div>代理根据其种类，在适当的时候向RealSubjuect转发请求。</div></li><li><div>代理不仅仅可以有自己的运算方法，通常情况下代理的职责并不一定单一，它可以组合其他的真是角色，也可以实现自己的职责，比如计算费用。代理类可以为真实角色预处理消息、过滤消息、消息转发、事后处理消息等功能。一个代理类可以代理多个真实角色，并且真实角色之间可以有耦合关系。</div></li><li><div>当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问，为了保证客户端的透明性，所访问的真实对象与代理对象需要实现相同的接口。</div></li><li><div>由于某些因素，客户端不想或不能直接访问一个对象，此时可通过“代理”的第三者实现间接访问。</div></li><li><div>代理模式引入了一个新的代理对象，代理对象在客户端对象和目标对象之间起到中介的作用，它去掉用户不能看到的内容和服务或者增添客户需要的额外的新服务。</div></li><li><div>通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身，这种实现机制即为代理模式，通过引入代理对象来间接访问一个对象，这就是代理模式的动机。</div></li><li><div>代理的目标是控制和管理访问。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="0.设计模式专题_files/Image [40].png" type="image/png" data-filename="Image.png" width="483"/></div><ul><li><div>Proxy：保存一个引用使得代理可以访问实体。若RealSubject和Subject的接口相同，Proxy会引用Subject。提供一个与Subject的接口相同的接口，这样代理就可以用来代替实体。控制对实体的存取，并可能负责创建和删除它。委托类、代理类，负责对真实角色的应用，把所有抽象主题类定义的方法限制委托给真实主题角色实现，并且在真实主题处理完毕前后做预处理和善后工作。</div><div>Proxy持有Subject的引用，所以必要时它可以将请求转发给Subject。</div><div>创建RealSubject，通常由Proxy负责。</div><div>客户和RealSubject的交互都必须通过Proxy，因为Proxy和RealSubject实现相同的接口，所以任何用到RealSubject的地方，都可以用Proxy取代。</div></li><li><div>Subject：定义RealSubject和Proxy的共用接口，这样就在任何使用RealSubject的地方都可以使用Proxy。可以是抽象类也可以是接口，最普通的业务类型定义。引入这一层抽象的原因是为了让客户端能够一致的对待真实对象和代理对象。</div></li><li><div>RealSubject：定义Proxy所代表的实体。即被代理的真实实体。被委托的角色，被代理的角色，是业务逻辑的具体执行者。</div><div>RealSubject通常是真正做事的对象，它是被proxy代理和访问控制的对象。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>职责清晰：真实角色是实现实际的业务逻辑，不用关心其他非本职责的事务，通过后期的代理完成一件事务。</div></li><li><div>高扩展性：具体主题角色随时会发生变化，只要其仍按接口实现，代理类完全可以在不修改的情况下使用。</div></li><li><div>智能化：体现为动态代理。</div></li><li><div>能够协调调用者和被调用者，在一定程度上降低了系统的耦合度。</div></li><li><div>客户端可以针对抽象主题角色进行编程，增加和替换代理类无需修改源代码，符合开闭原则，系统具有较好的灵活性和可扩展性。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>由于在客户端和真实主题之间增加了代理对象，因此有些类型的代理模式可能会造成请求的处理速度变慢，如保护代理。</div></li><li><div>实现代理模式需要额外的工作，而且有些代理模式的实现过程较为复杂，如远程代理。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、常见的代理形式</span></div><div><span style="font-size: 10pt; font-weight: bold;">      </span><span style="font-size: 10pt;">真实世界中，代理模式有很多变体，这些变体都有共通点：都会将客户对主题施加的方法调用拦截下来。</span></div><ol><li><div><span style="font-weight: bold;">远程代理（Remote Proxy）</span></div></li></ol><ul><li><div>为一个对象在不同的地址空间提供局部代表。（代理在这里的意义是，为一个远程资源做本地的代替。）</div></li><li><div>远程代理实际上就是“远程对象的本地代表”</div></li><ul><li><div>远程对象：这是一种对象，是在不同的地址空间运行的远程对象</div></li><li><div>本地代表：是一种可以由本地方法调用的对象，其行为会转发到远程对象中</div></li></ul></ul><div style="margin-left: 40px;">客户对象所做的就像是在做远程方法调用，但其实只是调用本地堆中的“代理”对象上的方法 ，再由代理处理所有网络通信的底层细节。</div><ul><li><div>Remote Proxy对象负责对请求及其参数进行编码，并向不同地址空间中的实体发送已编码的请求。</div></li><li><div>远程代理附加的间接性可以隐藏一个对象存在于不同地址空间的事实。</div></li><li><div>远程代理不包含对实体的直接引用，而只是一个间接引用。如“主机ID，主机上的局部地址”。</div></li><li><div>为一个位于不同的地址空间的对象提供一个本地的代理对象，这个不同的地址空间可以是在同一台主机中，也可以在另一台主机中，又称为大使。</div></li><li><div>远程代理设计复杂，封装了底层网络通信和对远程对象的调用。</div></li><li><div>远程代理使客户端可以访问在远程主机上的对象，代理可以将网络细节隐藏起来，使客户端不必考虑网络的存在。</div></li><li><div>客户端完全可以认为被代理的远程业务对象是在本地而不是在远程，而远程代理对象承担了大部分的网络通信工作，并负责对远程业务方法的调用。</div></li><li><div>客户端无法直接访问远程主机中的业务对象，远程业务对象在本地主机中有一个代理对象，代理对象负责对远程业务对象的访问和网络通信，对于客户端对象而言是透明的。</div></li><li><div>客户端无需关心实现具体业务的是谁，只需按照服务接口定义的方式直接与本地主机中的代理对象交互即可。</div></li><li><div><span style="font-weight: bold;">优点</span>：为位于两个不同地址空间的对象的访问提供了一种实现机制，可以将一些消耗资源较多的对象和操作移至性能更好的计算机上，提供系统整体运行效率。</div></li></ul><ol start="2"><li><div><span style="font-weight: bold;">虚代理（Virtual Proxy）</span></div></li></ol><ul><li><div>根据需要创建开销很大的对象。（按需创建）</div></li><li><div>虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时，由虚拟代理来扮演对象的替身。对象创建后，代理就会将请求直接委托给对象。</div></li><li><div>Virtulal Proxy可以缓存实体的附加信息，以便延迟对它的访问。</div></li><li><div>虚代理附加的间接性可以进行近行最优化，例如根据要求创建对象。</div></li><li><div>虚代理开始的时候使用一个间接引用，例如一个文件名，但最终将获取并使用一个直接引用。</div></li><li><div>如果需要创建一个资源消耗较大的对象，先创建一个消耗相对较小的对象来表示，真实对象只在需要时才会被真正创建。</div></li><li><div>对于一些占用系统资源较多或加载时间较长的对象，可以给这些对象提供一个虚代理。</div></li><li><div>在真实对象创建成功之前，虚代理扮演真实对象的替身，在真实对象创建成功之后，虚代理将用户请求转发给真实对象。</div></li><li><div>以下两种情况下可以使用虚代理：</div></li></ul><div style="padding-left: 30px;">a. 由于对象本身的复杂性或网络等原因导致一个对象需要较长的加载时间，此时可以用一个加载时间相对较短的代理对象来代表真实对象。</div><div style="padding-left: 30px;">在实现时可以结合多线程技术，一个线程用于显示代理对象，其他线程用于加载真实对象。在程序启动时，用代理对象代替真实对象初始化，大大加快了启动时间，后续需要真实对象时，由代理对象引用，此时真实对象加载完毕。</div><div style="padding-left: 30px;">b. 当一个对象的加载十分耗费系统资源时。</div><div style="padding-left: 30px;">虚代理让那些占用大量内存或处理起来非常复杂的对象推迟到使用他们的时候才创建，而在此之前用一个相对来说占用资源较少的代理对象来引用真实对象。</div><ul><li><div>虚代理都是用一个“虚假”的代理对象来表达真实对象，通过代理对象来间接引用真实对象，可以在一定程度上提高系统的性能。</div></li><li><div><span style="font-weight: bold;">优点</span>：通过一个消耗资源较少的对象来代表一个消耗资源较多的对象，可以在一定程度上节省系统运行开销。</div></li></ul><ol start="3"><li><div><span style="font-weight: bold;">保护代理（Protect Proxy）</span></div></li></ol><ul><li><div>控制对原始对象的访问。</div></li><li><div>保护代理是一种根据访问权限决定客户可否访问对象的代理。</div></li><li><div>保护代理用于对象应该有不同的访问权限的时候。（也是代替，所有人通过代理访问对象。）</div></li><li><div>Protection Proxy检查调用者是否具有实现一个请求所必需的访问权限。</div></li><li><div>保护代理附加的间接性允许在访问一个对象时有一些附加的内务处理。</div></li><li><div>控制对一个对象的访问，可以给不同的用户提供不同级别的使用权限。</div></li><li><div><span style="font-weight: bold;">优点</span>：可以控制对一个对象的访问权限为不同的用户提供不同级别的使用权限。</div></li></ul><ol start="4"><li><div><span style="font-weight: bold;">智能指引代理（Smart Reference Proxy）</span></div></li></ol><ul><li><div>取代了简单的指针，在访问对象时执行一些附加操作。</div></li><li><div>当主题被引用时，进行额外的动作，例如计算一个对象被引用的次数。</div></li><li><div>典型用途包括：1）对指向实际对象的引用计数，这样当该对象没有引用时，可以自动释放它（也称为Smart_Pointer）。2）当第一次引用一个持久对象时，将它装入内存。3）在访问一个实际对象前，检查是否已经锁定了它，以确保其他对象不能改变它（线程安全）。</div></li><li><div>智能指引代理附加的间接性允许在访问一个对象时有一些附加的内务处理。</div></li><li><div>当一个对象被引用时，提供一些额外的操作，如将对象被调用次数记录下来等。</div></li></ul><ol start="5"><li><div><span style="font-weight: bold;">缓冲代理（Cache Proxy）</span></div></li></ol><ul><li><div>为某一个目标操作的结果提供临时的存储空间，以便多个客户端可以共享这些结果。从而可以避免某些方法的重复执行，优化系统性能。</div></li><li><div>常见于web服务器代理，以及内容管理与出版系统。</div></li><li><div>为开销大的运算结果提供暂时的存储：它也允许多个客户共享结果，以减少计算或网络延迟。</div></li><li><div><span style="font-weight: bold;">优点</span>：为某一个操作的结果提供临时的缓存存储空间，以便在后续使用中能够共享这些结果，优化系统性能，缩短执行时间。</div></li></ul><ol start="6"><li><div><span style="font-weight: bold;">其他衍生代理</span></div></li></ol><div style="margin-left: 40px;">1）<span style="font-weight: bold;">Copy-on-write代理</span></div><div style="margin-left: 40px;">- Copy-on-write代理于根据需要创建对象有关（虚代理）。</div><div style="margin-left: 40px;">- 用来控制对象的复制，方法是延迟对象的复制，直到客户真的需要为止。这是虚拟代理的变体。</div><div style="margin-left: 40px;">- 拷贝一个庞大而复杂的对象是一种开销很大的操作，如果这个拷贝根本没有被修改，那么这些开销就没有必要。用代理延迟这一拷贝过程，可以保证只有当这个对象被修改的时候才对它进行拷贝。</div><div style="margin-left: 40px;">- 在实现Copy-on-write时必须对实体进行引用计数。拷贝代理仅会增加引用计数。只有当用户请求一个修改该实体的操作时，代理才会真正的拷贝它。在这种情况下，代理还必须减少实体的引用计数。当引用的树木为零时，这个实体将被删除。</div><div style="margin-left: 40px;">- Copy-on-write可以大幅度的降低拷贝庞大实体时的开销。</div><div style="margin-left: 40px;">- 是虚代理的一种，把复制操作延迟到只有在客户端真正需要的时候才执行。一般来说，对象的深克隆是一个开销较大的操作，Copy-on-write代理可以让这个操作延迟，只有对象被用到的时候才被克隆。</div><div style="margin-left: 40px;">2）<span style="font-weight: bold;">防火墙代理（Fire Wall Proxy）</span></div><div style="margin-left: 40px;">- 保护目标不被恶意用户接近。</div><div style="margin-left: 40px;">- 常见于公司的防火墙系统。</div><div style="margin-left: 40px;">- 控制网络资源的访问，保护主题免于“坏客户”的侵害。</div><div style="margin-left: 40px;">3）<span style="font-weight: bold;">同步化代理（Synchronization Proxy）</span></div><div style="margin-left: 40px;">- 使几个用户能够同时使用一个对象而没有冲突。</div><div style="margin-left: 40px;">- 常见于JavaSpaces，为分散式环境内的潜在对象集合提供同步访问控制。</div><div style="margin-left: 40px;">- 在多线程的情况下为主题提供安全的访问。</div><div style="margin-left: 40px;">4）<b>复杂隐藏</b><span style="font-weight: bold;">代理（Complexity Hiding Proxy）</span></div><div style="margin-left: 40px;">- 用来隐藏一个类的复杂集合的复杂度，并进行访问控制。</div><div style="margin-left: 40px;">- 也称为外观代理（Facade Proxy），复杂隐藏代理和外观模式是不一样的，因为代理控制访问，而外观模式只提供另一组接口。</div><div><span style="font-size: 14pt; font-weight: bold;">五、可能的改进</span></div><ul><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/proxy/DynamicProxy">动态代理。</a>动态代理在实现阶段不用关心代理谁，而在运行阶段才指定代理哪个对象，相对而言，其他的代理方式是静态代理。面向横切面的编程（AOP）其核心就是采用了动态代理机制。</div></li></ul><div style="margin-left: 40px;">- 动态代理的典型应用是Spring AOP；</div><div style="margin-left: 40px;">- 传统代理模式中，真实主题角色必须是事先已经存在的，并将其作为代理对象的内部成员属性。</div><div style="margin-left: 40px;">- 动态代理主要解决：如何在事先不知道真实主题角色的情况下使用代理主题角色。</div><div style="margin-left: 40px;">- 动态代理在实现阶段不用关心代理谁，而在运行阶段才指定代理哪个对象，相对而言，其他的代理方式是静态代理。</div><div style="margin-left: 40px;">- 动态代理的主要意图就是解决常说的横切面编程问题，在不改变已有代码结构的情况下，增强或控制对象的行为。</div><div style="margin-left: 40px;">- 在学习AOP框架时，认识清楚几个名词即可：切面（Aspect）、切入点（JionPoint）、通知（Advice）、织入（Weave）。</div><div style="margin-left: 40px;">Java在java.lang.reflect包中有自己的代理支持，利用这个包你可以在运行时动态的创建一个代理类，实现一个或多个接口，并将方法的调用转发到你所指定的类，因为实际的代理类是在运行时创建的，这个Java技术就是动态代理。</div><div style="margin-left: 40px;">动态代理类图如下。</div><div style="margin-left: 40px;"><img src="0.设计模式专题_files/Image [41].png" type="image/png" data-filename="Image.png"/></div><div style="margin-left: 40px;">因为Java已经为你创建了Proxy类，所以你需要有办法来告诉Proxy类你要做什么。你不能像以前一样把代码放在Proxy类中，因为 Proxy不是你直接实现的。既然这样的代码不能放在Proxy类中，那么要放在哪里？放在 Invocation Handler中。 InvocationHandler的工作是响应代理的任何调用。你可以把InvocationHandler想成是代理收到方法调用后，请求做实际工作的对象。</div><div style="margin-left: 40px;">动态代理之所以被称为动态，是因为运行时才将它的类创建出来。代码开始执行时，还没有proxy类，它是根据需要从你传入的接口集创建的。</div><ul><li><div>代理模式与工厂模式联合使用</div><div>一个常用的技巧是提供一个工厂，实例化并返回主题。因为这是在工厂方法内发生的，我们可以用代理包装主題再返回，而客户不知道也不在乎他使用的是代理还是真东西。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">六、个人理解</span></div><ul><li><div>代理模式思路比实现重要，且在现代编程过程中使用异常频繁。</div></li><li><div>代理模式有很多典型场景，主要有：远程代理，虚拟代理，保护代理，缓冲代理，智能引用代理。</div></li></ul><div style="margin-left: 40px;">基于以上几个代理衍生出：Copy-on-write代理，防火墙代理，同步化代理</div></div><div style="margin-left: 40px;">以及代理模式的重要扩展：动态代理。</div><ul><li><div>代理归根结底是<span style="background-color: rgb(255, 250, 165); color: rgb(227, 0, 0);-evernote-highlight:true;">代替、替换</span>的意思，万变不离其宗，代理实际上就是用另一个对象代替一个还不方便或需扩展的对象。</div></li><li><div>有的代理在客户端定义具体主题的对象，有的代理在代理的构造函数中定义具体主题的对象。</div></li></ul><div style="margin-left: 40px;">个人认为应该具体场景具体分析，按需要进行选择，没有一定之规。</div><div style="margin-left: 40px;">实际上，<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;">个人认为代理模式的思想比实现方法更重要！</span></div><div><br clear="none"/></div></div><div><br/></div></span>
</div>
<hr>
<a name="22997"/>
<h1>11.享元（Flyweight）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/6/28 11:06</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/11 0:09</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>享元模式运用共享技术有效的支持大量细粒度的对象。</div></li><li><div>又称为蝇量模式。</div></li><li><div>flyweight模式描述了如何共享对象，使得可以细粒度的使用它们而不需要高昂的代价。</div></li><li><div>flyweight不能对它所运行的场景做出任何假设，这里的关键概念是内部状态和外部状态之间的区别。<span style="font-weight: bold;">内部状态</span>存储于flyweight当中，它包含了独立于flyweight场景的信息，这些信息使得flyweight可以被共享。而外部状态取决于flyweight场景，并根据场景而变化，因此不可共享。用户对象负责在必要的时候讲外部状态传递给flyweight。</div></li><li><div>Flyweight模式对那些通常由于数量太大而难以用对象来表示的概念或实体进行建模。</div></li><li><div>Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。</div></li><li><div>flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象之中，而外部对象则由Client对象存储或计算。当用户调用flyweight对象的操作时，将状态传递给它。</div></li><li><div>用户不应直接对ConcreteFlyweight类进行实例化，而只能从FlyweightFactory对象得到ConcreteFlyweight对象，这可以保证对它们适当的进行共享。</div></li><li><div>共享还意味着某种形式的引用计数和垃圾回收，这样当一个flyweight不再使用时，可以回收它的存储空间。然而，当flyweight的数目固定而且很小的时候，这两种操作都不必要。在这种情况下，flyweight完全可以永久保存。</div></li><li><div>享元模式可以避免大量非常相似类的开销，在程序设计中，有事需要生成大量的细粒度的类实例来表示数据，如果能发现这些实例除了几个参数外基本相同，有事可大幅减少需实例化的类的数量，如果能把那些参数移到类实例的外面，在方法调用时将它们传递过来，就可以通过共享，大幅减少单个实例的数目。</div></li><li><div><span style="font-weight: bold;">适用</span>：如果一个应用程序使用了大量的对象，而大量对象造成了很大的存储开销时，就应该考虑使用；另外，对象的大多数状态可以是外部状态，如果删除对象的外部状态，可以用相对较少的共享对象取代很多组对象，此时可以考虑使用享元模式。</div></li><li><div>享元模式更多时是一种底层设计模式，在现实中也有应用，如各种棋类游戏。</div></li><li><div>享元模式提出了两个要求：细粒度的对象和共享对象。</div></li><li><div>Flyweight是拳击比赛中的特有名词，意思是“特轻量级”，指的是51公斤级的比赛，用到设计模式中是指类要轻量级，粒度要小，这才是Flyweight要表达的意思，粒度小了，带来的问题是对象太多，那就用共享技术来解决。所以享元模式也被称为轻量级模式。</div></li><li><div>正因为区分了内部状态和外部状态，可以将具有相同内部状态的对象存储在享元池中，享元池中的对象是可以实现共享的，需要的时候就将对象从享元池中取出，实现对象的复用，通过向取出对象注入不同的外部状态，可以得到一系列相似的对象，而这些对象在内存中实际上只存储一份。</div></li><li><div>如想让某个类的一个实例能够用来提供许多“虚拟实例”，就使用蝇量模式。</div></li><li><div>当一个类有许多实例，而这些实例能被同一方法控制的时候，我们就可以使用蝇量模式。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div><img src="0.设计模式专题_files/Image [42].png" type="image/png" data-filename="Image.png" width="401"/></div><ul><li><div>Flyweight：描述一个接口，通过这个接口，flyweight可以接受并作用于外部状态。这个类是所有享元类的超类或接口。接受一个外部状态（类图中的extrinsicState），然后针对外部状态进行一些操作（类图中的Operation操作）。这是一个抽象类，可以把外部状态和内部状态定义出来，避免子类的随意扩展。享元类的设计是享元模式的要点之一，在享元类中要将内部状态与外部状态分开处理，通常内部状态作为享元类的成员变量，外部状态通过注入的方法添加到享元类中。</div></li><li><div>ConcreteFlyweight：实现Flyweight接口，并未内部状态（如果有的话）增加存储空间。ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的，即它必须独立于ConcreteFlyweight对象的场景。具体享元角色，实现抽象角色定义的业务，需注意内部状态的处理应与环境无关，不应出现一个操作改变了内部状态，同时修改外部状态。</div></li><li><div>UnsharedConcreteFlyweight：并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能，但它并不强制共享。在flyweight对象结构的某些层次，UNsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点。不存在内部状态或安全要求（线程安全），不能够使用共享技术的对象，一般不出现在享元工厂中。</div></li><li><div>FlyweightFactory：享元工厂。创建并管理flyweight对象。确保合理的共享flyweight。当用户请求一个flyweight时，FlyweightFactory对象提供一个已创建的实例或者创建一个新的实例（如果不存在的话）。构建一个池容器，同时提供从池中获得对象的方法。主要目的是提供一个用于存储享元对象的享元池。</div></li><li><div>Client：维持一个对flyweight的引用。计算或存储一个（多个）flyweight的外部状态。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>使用享元模式，有共享的对象，实例总数大大减少，如果共享的状态越多，存储节约也越多，节约量随共享状态的增多而增大。可以大大减少应用程序创建的对象，降低程序内存占用，增强程序的性能。</div></li><li><div>享元模式的外部状态相对独立，而且不会影响其内部状态，从而使得享元对象可以在不同的环境中被共享。</div></li><li><div>将许多“虚拟”对象的状态集中管理。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>使用享元模式需要维护一个记录了系统已有的所有享元的列表，这本身需消耗资源，另外享元模式使得系统更加复杂，为了使对象可以共享，需要将一些状态外部化，这使程序的逻辑复杂化。</div></li><li><div>为了使对象可以共享，享元模式需要将享元对象的部分状态外部化，从而读取外部状态将使得运行时间变长。</div></li><li><div>一旦你实现了蝇量模式，那么单个的逻辑实例将无法拥有独立而不同的行为。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、个人理解</span></div><ul><li><div>使用享元时，思路与抽象很重要，如何具体实现反而比较灵活，相对不太重要。也就是说，享元模式的思想比起如何实现更加重要。</div></li><li><div>享元模式的核心目标是节省存储空间，可以算是用时间换取空间，但是特定情况下（比如内存使用量很大，发生颠簸的时候），节省空间也就是节省时间。</div></li><li><div>享元模式比较复杂，要区分内部状态与外部状态，其实现更加复杂。、</div></li><li><div>在享元模式中，由于对象的共享，一定要考虑到线程安全的问题。</div></li><li><div>应用享元模式时，需要考虑两个维度：1）同时继承Flyweight抽象类的有ConcreteFlyweight和UnsharedConcreteFlyweight，即有可以共享的类和不可以共享的类。2）在ConcreteFlyweight类，即可以共享的类中，有可以共享的内部状态（intrinsicState）和不可以共享的外部状态（extrinsicState）。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div></div><div><br/></div></span>
</div>
<hr>
<a name="23448"/>
<h1>13.职责链（Chain Of Responsibility）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/7/12 11:05</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/11 0:04</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>使多个对象都有机会处理请求，从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链，并沿着这条链传递该请求，直到有一个对象处理它为止。</div></li><li><div>这一模式的想法是，给多个对象处理一个请求的机会，从而解耦发送者和接收者，该请求沿对象链传递直至其中一个对象处理它。</div></li><li><div>从第一个对象开始，链中收到请求的对象要么亲自处理它，要么转发给链中的下一个候选者。提交请求的对象并不明确的知道哪一个对象将会处理它——该请求有一个隐式的接收者。</div></li><li><div>职责链中，发出请求的客户端并不知道当中的哪一个对象最终处理这个请求，这样系统的更改可以在不影响客户端的情况下动态的重新组织和分配责任。</div></li><li><div>职责链使得接收者和发送者都没有对方的明确信息，且链中的对象自己也并不知道链的结构，结果是职责链可简化对象的相互连接，它们仅需保持一个指向其后继者的引用，而不需保持它所有的候选接收者的引用。</div></li><li><div>可以随时的增进或修改处理一个请求的结构，增强了给对象指派职责的灵活性。</div></li><li><div>一个请求极有可能到了链的末端都得不到处理，或者因为没有正确配置而得不到处理。</div></li><li><div>职责链实现的两个重点：1）设置后继者；2）对于每个请求，做出判断，是处理请求，还是转给后继者处理。</div></li><li><div>职责链模式的重点在“链”上，由一条链去处理相似的请求，在链中决定谁来处理这个请求，并返回相应的结果。</div></li><li><div>职责链的核心在于抽象处理者类（Handler）的设计。</div></li><li><div>职责链模式并不创建职责链，职责链的创建工作必须由系统的其他部分来完成。</div></li><li><div>职责链模式可分为纯职责链模式和不纯的职责链模式两种：</div></li></ul><div style="margin-left: 40px;">1）纯职责链：要求一个具体处理者对象只能在两个行为中选择一个：要么承担全部责任，要么将职责推给下家，不允许出现某一个具体处理者对象在承担了一部分或全部责任，要么将责任推给下家不允许出现某一个具体处理者对象在承担了一部分或全部责任后又将责任向下传递的情况。</div><div style="margin-left: 120px;">而且在纯职责链模式中，要求一个请求必须被某一个处理者对象所接收，不能出现某个请求未被任何一个处理者对象处理的情况。</div><div style="margin-left: 40px;">2）不纯职责链：允许某个请求被一个具体处理者部分处理后再向下传递，或者一个具体处理者处理完某个请求后其后继处理者可以继续处理该请求，而且一个请求可以最终不被任何处理者对象接收。</div><div style="margin-left: 40px;">3) 在责任链中一个请求发送到链中后，前一节点消费部分信息，然后交由后续节点继续处理，最终可以有处理结果，也可以没有处理结果，可以不用理会什么纯的、不纯的责任链模式。</div><ul><li><div>在实际应用中，一般会有一个封装类对责任链模式进行封装，替代Client类，直接返回链中的第一个处理者，具体链的设置不需要高层次的模块关系。（也就是说有一个单独的类来封装整个链，设置下一个节点，Client主要与这个类打交道）</div></li><li><div>每个ConcreteHandler类只要关注自己的业务逻辑即可，至于什么事要自己处理，让父类去决定，也即父类实现了请求传递的功能，子类实现请求的处理，符合单一职责原则，各个实现类只完成一个动作或逻辑，也即只有一个原因引起的改变。</div></li><li><div>责任链屏蔽了请求的处理过程，发起一个请求到底是谁处理的，不需关心，只要把请求抛给责任链的第一个处理者，最终会返回一个处理结果（当然也可以不做任何处理），作为请求者可以不用知道到底需要谁来处理的，这是责任链的核心。</div></li><li><div>发出请求的客户端并不知道链上的哪一个对象最终处理这一请求，这使得系统可以在不影响客户端的情况下动态重组链和分配责任。</div></li><li><div>职责链模式并不创建职责链，职责链的创建工作必须由系统其它部分来完成。</div></li><li><div>当你想要让一个以上的对象有机会能够处理某个请求的时候，就使用责任链模式。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="0.设计模式专题_files/Image [43].png" type="image/png" data-filename="Image.png" width="513"/></div><div style="text-align: center;">类图</div><div style="text-align: center;"><img src="0.设计模式专题_files/Image [44].png" type="image/png" data-filename="Image.png" width="639"/></div><div style="text-align: center;">对象图</div><ul><li><div>Handler：定义一个处理请求的接口；（可选）实现后继链。职责链的核心在于引入了一个抽象处理者。</div></li><li><div>ConcreteHandler：处理它所负责的请求。可访问它的后继者。如果可处理该请求，就处理之；否则将该请求转发给它的后继者。</div></li><li><div>Client：向链上的具体处理者（ConcreteHandler）对象提交请求。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div><span style="font-weight: bold;">降低耦合度</span>：将请求和处理分开，该模式使得一个对象无需知道是其他哪一个对象处理其请求。对象仅需要知道该请求会被“正确”地处理。接收者和发送者都没有对方的明确信息，且链中的对象不需要知道链的结构。结果是，职责链可简化对象的相互连接。它们仅需要保持一个指向其后继的引用，而不需要保持它所有的候选接收者的引用。</div></li><li><div><span style="font-weight: bold;">增强了给对象指派职责的灵活性</span>：当在对象中分派职责时，职责链给你更多的灵活性。你可以通过在运行时对该链进行动态的增加或修改来增加或改变处理一个请求的那些职责。你可以将这种机制与静态的特例化的处理对象的继承机制结合起来使用。</div></li><li><div>请求处理对象仅需维持一个指向其后继者的引用，而不需要维持它对所有的候选处理者的引用，可简化对象的相互连接。</div></li><li><div>在系统中增加一个新的具体请求处理者时无需修改原有系统的代码，只需在客户端重新建链即可，符合“开闭原则”。</div></li><li><div>通过改变链内的成员或调动他们的次序，允许你动态的新增或者删除责任。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div><span style="font-weight: bold;">不保证被接受</span>：既然一个请求没有明确的接收者，那么就不能保证它一定会被处理——该请求可能一直到链的末端都得不到处理。一个请求也可能因该链没有被正确配置而得不到处理。</div></li><li><div><span style="font-weight: bold;">性能问题</span>：每个请求都从链头遍历到链尾，特别链比较长的时候，性能是一个非常大的问题。</div></li><li><div><span style="font-weight: bold;">调试不方便</span>：特别是链条较长，环节较多的时候，由于采用了类似递归的方式，调试的时候逻辑可能比较复杂。可能不容易观察运行时的特征，有碍于除错。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、个人理解</span></div><ul><li><div>发送一个请求到链中，链上的对象依次接收请求，如果能处理就处理，不能处理就向下传递这个请求，直到链结束或请求被处理。</div></li></ul><div><br clear="none"/></div></div><div><br/></div></span>
</div>
<hr>
<a name="23705"/>
<h1>14.命令（Command）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/7/24 16:22</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/29 6:58</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>将一个请求封装为一个对象，从而使你可用不同的请求对客户进行参数化，对请求排队或记录请求日志，以及支持可撤销操作。</div></li><li><div>队列、日志和支持撤销操作时基本命令模式相当直接的扩展。</div></li><li><div>又称为动作（action）模式，事物（transaction）模式</div></li><li><div>有时必须向某对象提交请求，但并不知道关于被请求的操作或请求的接收者的任何信息，命令模式将请求本身变成一个对象，这个对象可被存储并像其他对象一样被传递。</div></li><li><div>命令模式的关键是一个抽象的Command类，它定义了一个执行操作的接口。其最简单的形式是一个抽象的Execute操作，具体的Command子类将接收者作为它的一个实例变量，并实现Execute操作，指定接收者采取的动作，而接收者有执行该请求所需的具体信息。</div></li><li><div>可以将几个命令组成更大的命令的形式来支持命令脚本。</div></li><li><div>命令模式中，提交一个请求的对象仅需要知道如何提交它，而不需要知道该请求将会被如何执行。</div></li><li><div>命令模式的适用性：</div></li></ul><div style="padding-left: 30px;">1）需要抽象出待执行的动作参数化某对象。可用回调（CallBack）函数表达这种参数化机制。</div><div style="padding-left: 30px;">2）在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。</div><div style="padding-left: 30px;">3）支持取消操作。Command的Execute操作可在实施操作前将状态存储起来，在取消操作时，这个状态用来消除该操作的影响。</div><div style="padding-left: 30px;">4）支持修改日志。当系统崩溃时，这些修改可以被重做一遍。</div><div style="padding-left: 30px;">5）<span style="font-weight: bold;">用构建在原语操作上的高层操作构造一个系统。</span>这样一种结构在支持事务的信息系统中很常见。一个事务封装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口，使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。</div><ul><li><div><span style="font-weight: bold;">应避免撤销操作过程中的错误累积</span>。在实现一个可靠的、能保持原先语义的撤销/重做机制时，可能会遇到滞后影响的问题。由于命令的重复执行、取消执行和重执行的过程中可能会积累错误，以致一个应用的状态最终偏离初始值。这就有必要在Command中存入更多的信息，以保证这些对象可被精确的复原成他们的初始状态。</div></li><li><div>对请求排队或记录请求日志，以及支持可撤销的操作等行为时，“行为请求者”与“行为实现者”的紧耦合是不太适合的。</div></li><li><div>敏捷开发的原则告诉我们，不要为代码添加基于猜测的，实际不需要的功能，如果不清楚一个系统是否需要命令模式，一般就不要着急去实现它，事实上，在需要的时候通过重构实现这个模式并不困难，只有在真正需要如撤销/恢复操作等功能时，把原来的代码重构为命令模式才有意义。</div></li><li><div>命令模式是一个高内聚的模式。</div></li><li><div>命令模式在项目中非常频繁的使用，因为它的封装性非常好，把请求方（Invoker）和执行方（Receiver）分开，扩展性也有很好的保障，通用代码比较简单。</div></li><li><div>在命令模式中，发送者与接收者之间引入了新的命令对象，将发送者的请求封装在命令对象中，再通过命令对象来调用接收者的方法。</div></li><li><div><span style="font-weight: bold;">命令模式的核心在于引入了命令类，通过命令类来降低发送者和接收者的耦合度，</span>请求发送者只需要指定一个命令对象，再通过命令对象调用请求接收者的处理方法。</div></li><li><div><span style="font-weight: bold;">命令模式的本质是对请求进行封装</span>，一个请求对应于一个命令，将发出命令的责任和执行命令的责任分隔开。</div></li><li><div>命令模式将封装带到一个全新的境界：把方法调用封装起来。</div></li><li><div>命令模式中，利用命令对象，可以把请求封装成一个特定对象。</div></li><li><div>命令模式可以类比餐厅：Invoker 对象类比女招待，Command 对象类比订单，Receiver 对象类比厨师。餐厅的女招待通过订单与厨师解耦。</div></li><li><div>命令可以将运算块打包（一个接收者和一组动作），然后将它传来传去，就像是一般的对象一样。现在，即使在命令对象被创建许久之后，运算依然可以被调用。我们可以利用这样的特性衍生一些应用，例如：日程安排、线程池、工作队列等。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="0.设计模式专题_files/截图录屏_选择区域_20200726130936.png" type="image/png" data-filename="截图录屏_选择区域_20200726130936.png" width="832"/></div><ul><li><div>Command：声明执行操作的接口。（类比订单）调用命令对象的execute()方法，就可以让接收者进行相关的动作。这个接口也可以拥有一个undo()方法。</div></li><li><div>ConcreteCommand：将一个接收者对象绑定于一个动作，调用接收者相应的操作，以实现Execute（接收命令，转发给receiver）。一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点，命令对象将动作和接收者包进对象中。这个对象只暴露出一个execute()方法，当此方法被调用的时候，接收者就会进行这些动作。从外面看，其他对象不知道究竟哪个接收者进行了哪些动作，只知道如果调用execute()方法，请求的目的就能达到。</div><div>ConcreteCommand定义了动作和接收者之间的绑定关系。调用者只要调用execute()就可以发出请求，然后由ConcreteCommand调用接收者的<span style="font-weight: bold;">一个或多个动作</span>。</div></li><li><div>Client：创建一个具体命令对象并设定它的接收者。（类比顾客）</div></li><li><div>Invoker：要求该命令执行这个请求。调用者，请求发送者，它通过命令对象来执行请求，一个调用者不需要在设计时确定其接收者，因此它只需要与抽象命令类之间存在关联关系即可，在程序运行时可以将一个具体命令对象注入其中，再调用具体命令对象的execute接口方法，从而实现间接调用请求接收者的相关操作。（类比女招待）</div><div>持有一个命令对象，并在某个时间点调用命令对象的execute()方法，将请求付诸执行。</div></li><li><div>Receiver：知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。Receiver可能是一个抽象类，因为接收者可能有多个——实际上，任何类无论是否有关系，都可以是Receiver。（类比厨师）</div></li><li><div>协作（书上标准协作）：</div></li></ul><div style="padding-left: 30px;">1）Client创建一个ConcreteCommand对象并指定它的receiver对象。</div><div style="padding-left: 30px;">2）某Invoker对象存储该ConcreteCommand对象。</div><div style="padding-left: 30px;">3）某Invoker通过调用Command对象的Execute操作来提交一个请求，若该命令是可撤销的，ConcreteCommand就在执行Execute操作之前存储当前状态以用于取消该命令。</div><div style="padding-left: 30px;">4）ConcreteCommand对象调用它的Receiver的一些操作以执行该请求。</div><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>Command模式将调用操作的对象与知道如何实现该操作的对象解耦。将请求一个操作的对象与知道怎么执行一个操作的对象分割开。</div><div>将“动作的请求者”从“动作的执行者”对象中解耦。将“发出请求的对象”和“接受与执行这些请求的对象”分隔开来。</div></li><li><div>Command是头等的对象。它们可像其他的对象一样被操纵和扩展。</div></li><li><div>命令模式可以较容易的设计一个命令队列。可将多个命令装配成一个组合命令。一般说来，组合命令是Composite模式的一个实例。</div></li><li><div>增加新的Command很容易，因为这无需改变已有的类。</div></li><li><div>在需要的情况下，可以较容易的将命令记入日志。</div></li><li><div>允许接收请求的一方决定是否要否决请求。</div></li><li><div>可以容易的实现对请求的撤销与重做。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>类膨胀问题：对于Command的子类，如果有N个命令，Command的子类就要有N个，类膨胀问题比较严重。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、可能的改进</span></div><ul><li><div>命令队列</div></li><li><div>撤销操作</div><div>Command类的改进：当命令支持撤销时，该命令就必须提供和execute()方法相反的undo()方法。不管execute()刚才做什么，undo()都会倒转过来。</div><div>Invoker类的改进：加入一个新的实例变量，用来追踪最后被调用的命令，然后，不管何时撤销按钮被按下，我们都可以取出这个命令并调用它的undo()方法。</div></li><li><div>请求日志</div></li><li><div>Meta Command Pattern：可以创建命令的宏，以便一次执行多个命令。</div><div>宏命令：宏命令又称为组合命令，是组合模式与命令模式联用的产物。当调用宏命令的execute方法时，将递归调用它所包含的每个成员命令的execute方法，一个宏命令将触发多个具体命令的执行，从而实现对命令的批处理。</div><div>可以制造一种新的命令，用来执行其他一对命令，而不只是执行一个命令。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">五、个人理解</span></div><ul><li><div>命令模式很常用，但是实现起来比较复杂。</div></li><li><div>命令模式的实现有很多种，特别体现在Invoker与Commad关联，Command与Receiver关联的实现位置。这充分体现了对于命令模式，思路比如何实现更重要。</div></li><li><div>命令模式应该可以扩展到大型系统的架构中。</div></li><li><div>命令模式的实现中，有一个Invoker类，一般情况下（简单场景中），似乎这个类是多余的、累赘的，但是对于稍微复杂的情况，特别是有改进的情况，通过Invoker来调用Command就会非常合理。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div></div><div><br/></div></span>
</div>
<hr>
<a name="23794"/>
<h1>15.解释器（Interpreter）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/8/3 7:17</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/11 0:15</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-weight: bold;">一、基本描述</span></div><ul><li><div>给定一个语言，定义它的文法的一种表示，并定义一个解释器，这个解释器使用该表示来解释句子。</div></li><li><div>这里的语言是指使用规定格式和语法的代码。</div></li><li><div>如果一种特定类型的问题发生的频率足够高，那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器，该解释器通过解释这些句子来解决该问题。</div></li><li><div>当有一个语言需要解释执行，并且可将该语言中的句子表示为一个抽象语法树时，可使用解释器模式。而当存在以下情况时该模式效果最好：</div></li></ul><div style="margin-left: 40px;">1）文法简单。对于复杂的文法，文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是最好的选择，它们无须构建抽象语法树即可解释表达式，这样可以节省空间而且还可能节省时间。</div><div style="margin-left: 40px;">2）效率不是关键问题。最高效的解释器通常不是通过直接解释语法分析树实现的，而是首先将它们转换成另一种形式。例如，正则表达式通常被转换成状态机。但即使在这种情况下，转换器也可用解释器模式实现，该模式仍是有用的。</div><div style="margin-left: 40px;">3） 重复发生的问题可以使用解释器模式。</div><ul><li><div>解释器模式并未解释如何创建一个抽象语法树，换言之，它不涉及语法分析。</div></li><li><div>解释器模式需要解决的是，如果一种特定的类型的问题发生的频率足够的高，那么可能就值得将该问题的各个实例表述为一个简单语言中的句子，这样就可以构建一个解释器，该解释器通过解释这些句子来解决该问题。</div></li><li><div>解释器模式就是用“迷你语言”来表现程序要解决的问题，以迷你语言写成“迷你程序”来表现具体的问题。</div></li><li><div>解释器模式是一种按照规定语法进行解析的方案， 在现在项目中使用较少。</div></li><li><div>尽量不要在重要的模块中使用解释器模式， 否则维护会是一个很大的问题。 在项目中可以使用shell、 JRuby、 Groovy等脚本语言来代替解释器模式， 弥补Java编译型语言的不足。</div></li><li><div>解释器模式描述了如何为简单的语言定义一个文法， 如何在该语言中表示一个句子， 以及如何解释这些句子。</div></li><li><div>解释器模式的先置技术：</div></li></ul><div style="margin-left: 40px;">1）表示一个语言的文法规则；</div><div style="margin-left: 40px;">2）构造一颗抽象语法树。</div><ul><li><div>解释器模式是一种使用频率相对较低但学习难度较大的设计模式， 它用于描述如何使用面向对象语言构成一个简单的语言解释器。</div></li><li><div>在某些情况下， 为了更好地描述某一些特定类型的问题， 我们可以创建一种新的语言， 这种语言拥有自己的表达式和结构， 即文法规则， 这些问题的实例将对应为该语言中的句子。 此时， 可以使用解释器模式来设计这种新的语言。</div></li><li><div>使用解释器模式为语言创建解释器：</div></li><ul><li><div>当你需要实现一个简单的语言时，就使用解释器模式定义语法的类，并用一个解释器解释句子。每个语法规则都用一个类代表。</div></li><li><div>要想解释这种语言，就调用每个表达式类型的 interpret() 方法。此方法需要传入一个上下文（ Context）——也就是我们正在解析的语言字符串输入流——然后进行比对并采取适当的动作。</div></li></ul><li><div>用途</div></li><ul><li><div>当你需要实现一个简单的语言时，使用解释器。</div></li><li><div>当你有一个简单的语法，而且简单比效率更重要时，使用解释器。</div></li><li><div>可以处理脚本语言和编程语言</div></li></ul></ul><div><span style="font-weight: bold;">二、图例</span></div><div><img src="0.设计模式专题_files/Image [45].png" type="image/png" data-filename="Image.png"/></div><ul><li><div>AbstractExpression：抽象表达式，声明一个抽象的解释操作，这个接口为抽象语法树中所有的节点所共享。抽象表达式是生成语法集合（也叫做语法树） 的关键， 每个语法集合完成指定语法解任务， 它是通过递归调用的方式， 最终由最小的语法单元进行解析完成。具体解释任务由各个实现类来完成。</div></li><li><div>TerminalExpression：终结符表达式，实现与文法中的终结符相关联的解释操作，一个句子中的每个终结符需要改类的一个实例。 通常， 终结符表达式比较简单， 主要是处理场景元素和数据的转换。终结符是语言的最小组成单位，不能再进行拆分。实现抽象表达式中所要求的接口，文法中每一个终结符都有一个具体的终结符表达式与之对应。通常，终结符表达式类比较简单，主要处理场景元素和数据的转换。通常终结符只有一个类，但是有多个对象。</div></li><li><div>NonterminalExpression：非终结符表达式，对文法中的每一条规则 R ::= R1R2...Rn 都需要一个NonterminalExpression类。为从R1到Rn的每个符号都维护一个AbstractExpression类型的实例变量。为文法中的非终结符实现解释操作，解释一般要递归的调用表示R1到Rn的那些对象的解释操作。每个非终结符表达式都代表了一个文法规则， 并且每个文法规则都只关心自己周边的文法规则的结果（注意是结果） ， 因此这就产生了每个非终结符表达式调用自己周边的非终结符表达式， 然后最终、 最小的文法规则就是终结符表达式， 终结符表达式的概念就是如此，不能够再参与比自己更小的文法运算了。非终结符都是一个完整的句子，包含一系列终结符与非终结符，可以进一步分解。每个非终结符表达式都会对其他表达式产生依赖。非终结符表达式根据逻辑的复杂程度而增加，原则上每一个规则都对应一个非终结符表达式。由于非终结符表达式中可以包含终结符表达式，也可以继续包含非终结符表达式。</div></li><li><div>Context：上下文、环境类，包含解释器之外的一些全局信息。 在解释器模式中， 环境类Context用于存储解释器之外的一些全局信息， 它通常作为参数被传递到所有表达式的解释方法interpret()中， 可以在Context对象中存储和访问表达式解释器的状态， 向表达式解释器提供一些全局的、 公共的数据， 此外还可以在Context中增加一些所有表达式解释器都共有的功能， 减轻解释器的职责。可能存储了需要解释的语句。通常在Context中包含了一个HashMap或ArrayList等类型的集合对象（也可直接由HashMap等集合类充当环境类），存储一系列公共信息，用于在具体的解释操作时从中获取相关信息，存储一系列公共信息，用于在具体的解释操作时，从中获取相关信息，当系统无需提供全局公共信息时，可以省略环境类，可根据实际情况决定是否需要环境类。</div></li><li><div>Client：客户，构建（或被给定）表示该文法定义的语言中一个特定的句子的抽象语法树。该抽象语法树由NonterminalExpression和TerminalExpression的实例装配而成。调用解释操作。通常Client是一个封装类，封装的结果就是传递进来一个规范语法文件，解析器分析后产生结果并返回，避免了调用者与语法解释器的耦合关系，</div></li><li><div>协作：</div></li></ul><div style="margin-left: 40px;">1）Client构建（或被给定）一个句子，它是NonterminalExpression和TerminalExpression的实例的一个抽象语法树，然后初始化上下文并调用解释操作。</div><div style="margin-left: 40px;">2）每一个非终结符表达式节点定义相应子表达式的解释操作。而各终结符表达式的解释操作构成了递归的基础。</div><div style="margin-left: 40px;">3）每一个节点的解释操作作用上下文来存储和访问解释器的状态。</div><div><span style="font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>易于改变和扩展文法。因为该模式使用类来表示文法规则，你可以使用继承来改变或扩展该文法。已有的表达式可被增量式的改变，而新的表达式可定义为旧表达式的变体。</div></li><li><div>易于实现文法。定义抽象语法树中各个节点的类的实现大体相似，这些类易于直接编写，通常他们也可用一个编译器或语法分析程序生成器自动生成。</div></li><li><div>增加了新的解表达式的方式。解释器模式使得实现新表达式“计算”变得容易。</div></li><li><div>将每一个语法规则表示成一个类，方便于实现语言。</div></li><li><div>因为语法由许多类表示，所以你可以轻易地改变或扩展此语言。</div></li><li><div>通过在类结构中加入新的方法，可以在解释的同时增加新的行为，例如打印格式的美化或者进行复杂的程序验证。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>复杂的文法难以维护。解释器模式为文法中的每一条规则至少定义了一个类（使用BNF定义的文法规则需要更多的类），因此包含许多规则的文法可能难以管理与维护。可应用其他的设计模式来缓解这一问题，但当文法非常复杂时，其他的技术如语法分析程序或编译器生成器更为合适。</div></li><li><div>解释器模式采用递归调用方法。 每个非终结符表达式只关心与自己有关的表达式， 每个表达式需要知道最终的结果， 必须一层一层地剥茧， 无论是面向过程的语言还是面向对象的语言， 递归都是在必要条件下使用的， 它导致调试非常复杂。 想想看， 如果要排查一个语法错误， 我们是不是要一个断点一个断点地调试下去， 直到最小的语法单元。</div></li><li><div>效率问题。 解释器模式由于使用了大量的循环和递归， 效率是一个不容忽视的问题， 特别是一用于解析复杂、 冗长的语法时， 效率是难以忍受的。</div></li><li><div>当语法规则的数目太大时，这个模式可能会变得非常繁杂。在这种情况下，使用解析器/编译器的产生器可能更合适。</div></li></ul><div><span style="font-weight: bold;">四、可能的改进</span></div><ul><li><div>与Flyweight模式联用：说明如何在抽象语法树中共享终结符。</div></li><li><div>与Iterator模式联用：解释器可用一个迭代器遍历该结构。</div></li><li><div>与Visitor模式联用：可用来在一个类中维护抽象语法树中的各个节点的行为。</div></li></ul><div><span style="font-weight: bold;">五、个人理解</span></div><ul><li><div>解释器模式在实际当中应用并不广泛。基本上能够使用解释器模式的场景都会有替代的工具，这些工具都是针对特定场景优化后形成的，并不需要解释器模式这样单独开发。</div></li><li><div>解释器模式干两部分的活：语言的表示和表示的解释。也即语言的文法和语句的语义分析都是由解释器完成，但是词法分析、语法分析从而构造一颗抽象语法树的过程却并不是解释器完成，怪不得解释器模式在实际中并不常用。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div></div><div><br/></div></span>
</div>
<hr>
<a name="24192"/>
<h1>16.迭代器（Iterator）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/8/31 15:18</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/20 5:45</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-weight: bold;">一、基本描述</span></div><ul><li><div>提供一种方法顺序访问一个聚合对象中的各个元素，而又不需要暴露该对象每部表示。</div></li><li><div>又称为游标模式。</div></li><li><div>迭代器模式的关键思想是将对列表的访问和遍历从列表对象中分离出来并放入一个迭代器对象中。迭代器类定义了一个访问该列表元素的接口，迭代器类对象负责跟踪当前的元素，即它知道哪些元素已经遍历过了。</div></li><li><div>迭代器中有几个基本的操作：</div></li></ul><div style="margin-left: 40px;">1）CurrentItem——返回列表中的当前元素</div><div style="margin-left: 40px;">2）First——操作初始化迭代器，使当前元素指向列表的第一个元素</div><div style="margin-left: 40px;">3）Next——操作将当前元素指针向前推进一步，指向下一个元素</div><div style="margin-left: 40px;">4）IsDone——检查是否已越过最后一个元素，也就是完成了这次遍历<br/>
java.util.Iterator中定义了不同的基本操作：<br/>
1）next()</div><div style="margin-left: 40px;">2) hasNext()</div><div style="margin-left: 40px;">3) remove()</div><ul><li><div>将遍历机制与列表对象分离使我们可以定义不同的迭代器来实现不同的遍历策略，而无需在列表接口中列举它们。</div></li><li><div>迭代器和列表是耦合在一起的，而且客户对象必须知道遍历的是一个列表而不是其他聚合结构。</div></li><li><div>以下情况可以使用迭代器模式：</div></li></ul><div style="margin-left: 40px;">1）访问一个聚合对象的内容而无须暴露它的内部表示</div><div style="margin-left: 40px;">2）支持对聚合对象的多种遍历</div><ul><li><div>迭代器根据谁控制迭代可以分为外部迭代器（由客户来控制迭代）和内部迭代器（由迭代器控制迭代）。</div></li><li><div>当需要访问一个聚焦对象，而且不管这些对象是什么都需要遍历的时候，就应该考虑用迭代器模式。</div></li><li><div>迭代器模式为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。</div></li><li><div>当前，迭代器模式实用价值远不如学习价值大，因为现在高级编程语言如C#、Java等本身已经把这个模式做在语言中了。迭代器模式目前已经是一个没落的模式，作为一个最基础的工具，基本上没有人会单独写一个迭代器。</div></li><li><div>迭代器模式分离了集合对象的遍历行为，抽象出一个迭代器类来负责，这样既可以做到不暴露集合的内部结构，又可以让外部代码透明的访问集合内部的数据。</div></li><li><div>迭代器模式在访问数组、集合、列表 等数据时，尤其是在数据库数据操作时，是非常普遍的应用，但由于它太普遍了，所以各种高级语言都对它进行了封装，所以反而给人感觉此模式本身不太常用了。</div></li><li><div>迭代器模式提供了遍历容器的方便性，容器只要管理增减元素就行了，需要遍历时交由迭代器进行</div></li><li><div>简单的说，迭代器就类似于一个数据库中的游标，可以在一个容器内上下翻滚，遍历所有它需要查看的元素。</div></li><li><div>聚合类（Aggregate Class）是可以存储多个成员对象（元素）的类，对应的对象称为聚合对象。</div></li><li><div>通过引入迭代器，客户端无需了解聚合对象的内部结构即可实现对聚合对象中成员的遍历，还可以根据需要很方便的增加新的遍历方式。</div></li><li><div>将聚合类中负责遍历数据的方法提取出来， 封装到专<span style="font-size: unset; color: unset; font-family: unset;">门的类中， 实现数据存储和数据遍历分离， 无须暴露聚合类的内部属性即可对其进行操作，</span><span style="font-size: unset; color: unset; font-family: unset;">这正是迭代器模式的意图。</span></div></li><li><div>在迭代器模式结构中包含聚合和迭代器两个层次结构， 考虑到系统的灵活性和可扩展性， 在<span style="font-size: unset; color: unset; font-family: unset;">迭代器模式中应用了工厂方法模式。</span></div></li><li><div>在迭代器模式中， 提供了一个外部的迭代器来对聚合对象进行访问和遍历， 迭代器定义了一<span style="font-size: unset; color: unset; font-family: unset;">个访问该聚合元素的接口， 并且可以跟踪当前遍历的元素， 了解哪些元素已经遍历过而哪些</span><span style="font-size: unset; color: unset; font-family: unset;">没有。 迭代器的引入， 将使得对一个复杂聚合对象的操作变得简单。</span></div></li><li><div>在迭代器模式中应用了工厂方法<span style="font-size: unset; color: unset; font-family: unset;">模式， 抽象迭代器对应于抽象产品角色， 具体迭代器对应于具体产品角色， 抽象聚合类对应</span><span style="font-size: unset; color: unset; font-family: unset;">于抽象工厂角色， 具体聚合类对应于具体工厂角色。</span></div></li><li><div>在抽象迭代器中声明了用于遍历聚合对象中所存储元素的方法， 在<span style="font-size: unset; color: unset; font-family: unset;">具体迭代器中将实现抽象迭代器声明的遍历数据的方法。</span></div></li><li><div>需<span style="font-size: unset; color: unset; font-family: unset;">要注意的是抽象迭代器接口的设计非常重要， 一方面需要充分满足各种遍历操作的要求，</span><span style="font-size: unset; color: unset; font-family: unset;">尽量为各种遍历方法都提供声明， 另一方面又不能包含太多方法， 接口中方法太多将给子类</span><span style="font-size: unset; color: unset; font-family: unset;">的实现带来麻烦。（可以采用的策略：</span>在设计时要考虑全面， 避<span style="font-size: unset; color: unset; font-family: unset;">免之后修改接口。同时，使用抽象类来设计抽象迭代器，在抽象类中为每一个方法提供一个空的默认实现</span><span style="font-size: unset; color: unset; font-family: unset;">）</span></div></li><li><div>为了能够让迭代器可以访问到聚合对象中的数据，有两种实现的方式：</div></li></ul><div style="margin-left: 40px;">1）关联关系：在具体迭代器中维持一个对具体聚合对象的引用，以便访问存储在聚合对象中的数据。</div><div style="margin-left: 40px;">2）将迭代器类设计为聚合类的内部类，JDK中的迭代器类就是通过这种方法实现的。</div><ul><li><div>迭代器模式是一种使用频率非常高的设计模式，通过引入迭代器可以将数据的遍历功能从聚合对象中分离出来，聚合对象只负责存储数据，而遍历由迭代器完成。</div></li><li><div>迭代器让我们能够遍历聚合中的每个元素，而不会去强迫聚合必须提供方法，让我们在它的元素中游走。我们也可以在聚合的外面实现迭代器，换句话说，我们封装了遍历。</div></li><li><div>在设计中使用迭代器的影响是明显的：</div></li><ul><li><div>如果有一个统一的方法访问聚合中的每一个对象，就可以编写多态的代码和这些聚合搭配、使用</div></li><li><div>迭代器模式把在元素之间游走的责任交给迭代器，而不是聚合对象。这不仅让聚合的接口和实现变得简洁，也可以让聚合更专注在它所应该专注的事情上面（也就是管理对象集合），而不必去理会遍历的事情。让责任各得其所。</div></li></ul><li><div>迭代器的实现</div></li><ul><li><div>内部迭代器：有迭代器自行控制在元素间的游走，必须将操作传入迭代器，告诉迭代器在游走的过程中需要做什么事情，客户无法控制遍历的过程。</div></li><li><div>外部迭代器：一般常见的迭代器，客户通过next()取得下一个元素。</div></li></ul></ul><div><span style="font-weight: bold;">二、图例</span></div><div><img src="0.设计模式专题_files/Image [46].png" type="image/png" data-filename="Image.png" width="549"/></div><ul><li><div>Iterator（迭代器）：迭代器定义访问和遍历元素的接口。声明了用于遍历元素的方法。是迭代抽象类，用于定义得到开始对象（First）、得到下一个对象（Next）、判断是否到结尾（IsDone）、当前对象（CurrentItem）等抽象方法，统一接口。</div></li><li><div>ConcreteIterator（具体迭代器）：具体迭代器，实现迭代器接口。对该聚合遍历时跟踪当前位置。主要实现抽象迭代器接口，完成对聚合对象的遍历，同时在具体迭代器中通过游标来记录在聚合对象中所处的当前位置，在具体实现时，游标通常是一个表示位置的非负整数。</div></li><li><div>Aggregate（聚合）：聚合定义创建相应的迭代器对象的接口。聚合抽象类，存储和管理元素对象，声明了一个CreateIterator方法用于创建一个迭代器对象，充当抽象迭代器工厂角色。</div><div>有一个共同的接口供所有的聚合使用，这对客户代码是很方便的，将客户代码从集合对象的实现解耦了。</div></li><li><div>ConcreteAggregate（具体聚合）：具体聚合持有一个对象的集合，并实现创建相应迭代器的接口，该操作返回ConcreteIterator的一个适当的实例。</div></li><li><div>协作：</div></li><ul><li><div>ConcreteIterator跟踪聚合中的当前对象，并能够计算出待遍历的后继对象。</div></li><li><div>每一个具体聚合都要负责实例化一个具体迭代器，此迭代器能够遍历对象的集合。</div></li></ul></ul><div><span style="font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div><span style="font-weight: bold;">支持以不同的方式遍历一个聚合</span>。复杂的聚合可用多种方式进行遍历。迭代器模式使得改变遍历算法变的很容易：仅需用一个不同的迭代器的实例代替原先的实例即可。也可以自己定义迭代器的子类以支持新的遍历。</div></li><li><div><span style="font-weight: bold;">简化了聚合的接口</span>。有了迭代器的遍历接口，聚合本身就不再需要类似的遍历接口了，这样就简化了聚合的接口。</div></li><li><div><span style="font-weight: bold;">在同一个聚合上可以有多个遍历</span>。每个迭代器保持它自己的遍历状态，因此可以同时进行多个遍历。</div></li><li><div><span style="font-weight: bold;">满足“开闭原则”的要求</span>。在迭代器模式中，由于引入了抽象层，增加新的聚合类和迭代器类都很方便，无需修改原有代码。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>由于迭代器模式将存储数据和遍历数据的职责分离， 增加新的聚合类需要对应增加新的迭<span style="font-size: unset; color: unset; font-family: unset;">代器类， 类的个数成对增加， 这在一定程度上增加了系统的复杂性。</span></div></li><li><div>抽象迭代器的设计难度较大， 需要充分考虑到系统将来的扩展，在自定义迭代器时， 创建一个考虑全面<span style="font-size: unset; color: unset; font-family: unset;">的抽象迭代器并不是件很容易的事情。</span></div></li></ul><div><span style="font-weight: bold;">四、可能的改进</span></div><ul><li><div>可以采用<a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/iterator/JavaIterator">内置类</a>的方法来实现迭代器类，即迭代器类是聚合类的内置类。</div></li></ul><div><span style="font-weight: bold;">五、个人理解</span></div><ul><li><div>迭代器模式的应用极其广泛，但一般都内置到语言的基础设施当中，基本没有机会去自己开发一个迭代器。</div></li><li><div>一般迭代器要同工厂方法模式联用。</div></li><li><div>实际上迭代器并不复杂，甚至可以说非常简单。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="25243"/>
<h1>17.中介者（Mediator）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/9/6 5:24</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/11 0:21</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
<tr><td><b>来源：</b></td><td><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test"><i>https://github.com/PhycheLiu/DesignPattern_Study-Test</i></a></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-weight: bold;">一、基本描述</span></div><ul><li><div>用一个中介对象来封装一系列的对象交互，中介者使各对象不需要显式的相互引用，从而使其耦合松散，而且可以独立的改变它们之间的交互。</div></li><li><div>面向对象设计鼓励将行为分布到各个对象中，这种分布可能会导致对象间有许多连接。在最坏的情况下，每一个对象都知道其他所有对象。虽然将一个系统分割成许多对象通常可以增强可复用性，但是对象间相互连接的激增又会降低其可复用性。大量的相互连接使得一个对象似乎不太可能在没有其他对象的支持下工作——系统表现为一个不可分割的整体。对系统的行为进行较大的改动都十分困难，因为行为被分布在许多对象中，结果是，可能不得不定义很多子类以定制系统的行为。（通过增加子类来新增、修改功能）</div></li><li><div>可以通过将集体行为封装在一个单独的中介者对象中来避免这个问题。中介者负责控制和协调一组对象间的交互。中介者充当一个中介以使组中的对象不再相互显式引用。这些对象仅知道中介者，从而减少了相互连接的数目。</div></li><li><div>下列情况下可以考虑使用中介者模式：</div></li></ul><div style="margin-left: 40px;">1）一组对象以定义良好但复杂的方式进行通信，产生的相互依赖关系结构混乱且难以理解。</div><div style="margin-left: 40px;">2）一个对象引用其他很多对象并且直接与这些对象通信，导致难以复用该对象。</div><div style="margin-left: 40px;">3）想定制一个分布在多个类中的行为，而又不想生成太多的子类。</div><div style="margin-left: 40px;">4）多个对象有依赖关系， 但是依赖的行为尚不确定或者有发生改变的可能， 在这种情况<span style="font-size: unset; color: unset; font-family: unset;">下一般建议采用中介者模式， 降低变更引起的风险扩散。</span></div><div style="margin-left: 40px;"><span style="font-size: unset; color: unset; font-family: unset;">5）</span>产品开发。 一个明显的例子就是MVC框架， 把中介者模式应用到产品中， 可以提升产<span style="font-size: unset; color: unset; font-family: unset;">品的性能和扩展性， 但是对于项目开发就未必， 因为项目是以交付投产为目标， 而产品则是</span><span style="font-size: unset; color: unset; font-family: unset;">以稳定、 高效、 扩展为宗旨。</span></div><ul><li><div>可以有两种方法实现同事类与中介者类之间的通信：</div></li></ul><div style="margin-left: 40px;">1）使用观察者模式。将Mediator实现为一个Observer，各Colleague作为Subject，一旦其状态改变就发送通知给Mediator，Mediator做出的响应是将状态改变的结果传播给其他的Colleague。（正宗的SOA）</div><div style="margin-left: 40px;">2）在Mediator中定义一个特殊的通知接口，各Colleague在通信时直接调用该接口。</div><ul><li><div>中介者模式又叫做调停者模式，是中间人或调停者的意思。</div></li><li><div>中介者模式主要应用“迪米特法则”：如果两个类不必彼此直接通信，那么这两个类就不应当发生直接的相互作用，如果其中的一个类需要调用另一个类的某一个方法时，可以通过第三者转发这个调用。中介者模式是“迪米特法则”的一个典型应用。</div></li><li><div>通过中介者对象，可以将系统的网状结构变为以中介者为中心的星型结构，每个具体对象不再通过直接的联系与另一个对象发生作用，二是通过“中介者”对象与另一个对象发生互相作用。</div></li><li><div>中介者对象的设计，使得系统的结构不会因为新的对象的引入造成大量的修改工作。</div></li><li><div>根据未来是否有可能扩展中介者对象，可以考虑Mediator和ConcreteMediator是否合二为一，如果不存在扩展的情况，二者可以合并。</div></li><li><div>中介者模式很容易在系统中应用，也很容易在系统中误用。当系统出现了“多对多”交互复杂的对象群时，不要急于使用中介者模式，而要反思你的系统在设计上是否合理。</div></li><li><div>中介者模式的优点来自集中控制，其缺点也是集中控制。</div></li><li><div>在实际项目中，<span style="font-size: unset; color: unset; font-family: unset;">一般的做法是中介者按照职责进行划分， 每个中介者处理一个或多个类似的关联请求。</span></div></li><li><div>同事类一般使用构造函数注入中介者， 而中介者一般使用getter/setter方式注入同事类，<span style="font-size: unset; color: unset; font-family: unset;"> 这是因为同事类必须有中介者， 而中介者却可以只有部分同事类。</span></div></li><li><div>中介者模式简单， 但是简单不代表容易使用， 很容易被误用。 在面向对象的编程中， 对<span style="font-size: unset; color: unset; font-family: unset;">象和对象之间必然会有依赖关系，</span>类之间的依赖关系是必然存在的， 一个类依赖多个类的情况也是存在的。并不是只要有多个依赖关系就考虑使用中介者模式。如原本就简单的几个对象依赖关系， 如果为了使用模式而加入了中介<span style="font-size: unset; color: unset; font-family: unset;">者， 必然导致中介者的逻辑复杂化， 因此中介者模式的使用需要“量力而行”。 中介者模式适</span><span style="font-size: unset; color: unset; font-family: unset;">用于多个对象之间紧密耦合的情况， 紧密耦合的标准是： 在类图中出现了蜘蛛网状结构。 在</span><span style="font-size: unset; color: unset; font-family: unset;">这种情况下一定要考虑使用中介者模式， 这有利于把蜘蛛网梳理为星型结构， 使原本复杂混</span><span style="font-size: unset; color: unset; font-family: unset;">乱的关系变得清晰简单。</span></div></li><li><div>MVC框架， 其中的C（Controller） 就是一个中介者， 叫做前端<span style="font-size: unset; color: unset; font-family: unset;">控制器(Front Controller)， 它的作用就是把M(Model， 业务逻辑)和V（View， 视图） 隔离开，</span><span style="font-size: unset; color: unset; font-family: unset;">协调M和V协同工作， 把M运行的结果和V代表的视图融合成一个前端可以展示的页面， 减少</span><span style="font-size: unset; color: unset; font-family: unset;">M和V的依赖关系。</span></div></li><li><div>中介者对象的存在保证了对象结构上的稳定，也即系统的结构不会因为新对象的引入带来大量的修改工作。</div></li><li><div>中介者模式的核心在于中介者类的引入，在中介者模式中，中介者类承担了两方面的职责：</div></li></ul><div style="margin-left: 40px;">1）中转作用（ 结构性） ： 通过中介者提供的中转作用， 各个同事对象就不再需要显式引用其<span style="font-size: unset; color: unset; font-family: unset;">他同事， 当需要和其他同事进行通信时， 可通过中介者来实现间接调用。 该中转作用属于中</span><span style="font-size: unset; color: unset; font-family: unset;">介者在结构上的支持。</span></div><div style="margin-left: 40px;"><span style="font-size: unset; color: unset; font-family: unset;">2）</span>协调作用（ 行为性） ： 中介者可以更进一步的对同事之间的关系进<span style="font-size: unset; color: unset; font-family: unset;">行封装， 同事可以一致的和中介者进行交互， 而不需要指明中介者需要具体怎么做， 中介者</span><span style="font-size: unset; color: unset; font-family: unset;">根据封装在自身内部的协调逻辑， 对同事的请求进行进一步处理， 将同事成员之间的关系行</span><span style="font-size: unset; color: unset; font-family: unset;">为进行分离和封装。 该协调作用属于中介者在行为上的支持。</span></div><ul><li><div>当需要新增具体同事时，可以选择修改具体中介者类，也可以再次继承抽象中借着类（或继承原有具体中介者类），并覆盖其中定义的方法（即另外做一个新的具体中介者）。</div></li><li><div>中介者<span style="font-size: unset; color: unset; font-family: unset;">模式在事件驱动类软件中应用较为广泛， 特别是基于GUI（ Graphical User Interface， 图形用户</span><span style="font-size: unset; color: unset; font-family: unset;">界面） 的应用软件。</span></div></li><li><div>使用中介者模式来集中相关对象之间复杂的沟通和控制方式。</div></li><li><div>在需要持续追踪每个对象的每个规则，以及众多对象之间彼此错综复杂的关系的系统中，加入一个中介者，一切都变得简单了：</div></li><ul><li><div>每个对象都会在自己的状态改变时，告诉中介者。</div></li><li><div>每个对象都会对中介者所发出的请求作出回应。</div></li></ul></ul><div><span style="font-weight: bold;">二、图例</span></div><div><img src="0.设计模式专题_files/Image [47].png" type="image/png" data-filename="Image.png" width="649"/></div><ul><li><div><span style="font-weight: bold;">Mediator（中介者）：</span>中介者定义一个接口用于与各同事对象通信。当各Colleague仅与一个Mediator一起工作时，没有必要定义一个抽象的Mediator类。</div></li><li><div><span style="font-weight: bold;">ConcreteMediator（具体中介者）：</span>具体中介者通过协调各同事对象实现协作行为。了解并维护它的各个同事。必须依赖于各个同事角色，维护对各个同事对象的引用。实现抽象类Mediator的方法，从具体同事类接收消息，向具体同事对象发出命令。将调用同事类的方法，调用时可以增加一些自己的业务代码，将调用进行控制。</div></li><li><div><span style="font-weight: bold;">Colleague（抽象同事）：</span>定义各个同事类公有的方法，同时维护一个对抽象中介者类的引用，其子类可以通过该引用来与中介者通信。</div></li><li><div><span style="font-weight: bold;">ConcreteColleague（具体同事）：</span>每一个同事类都知道它的中介者对象。每一个同事类对象在需要与其他同事通信的时候，与它的中介者通信。每个同事类的行为分为两种，一种是同事本身的行为，如该表对象本身的状态，处理自己的行为等，与其他同事类或中介者没有任何依赖，这种行为叫做自发行为（Self-Method），另一种是必须依赖中介者才能完成的行为，叫做依赖方法（DepMethod）。每一个具体同事类只知道自己的行为，而不了解其他同事类的情况，但他们却都认识中介者对象。每一个同事对象在需要和其他同事对象通信的时候，先与中介者通信，通过中介者来简介完成与其他同事类的通信，同时，实现了抽象同事类中声明的抽象方法。</div></li><li><div><span style="font-weight: bold;">协作：</span>同事向一个中介者对象发送和接收请求，中介者在各同事间适当的转发请求以实现协作行为。</div></li></ul><div><span style="font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div><span style="font-weight: bold;">减少子类生成。</span>Mediator将原本分布于多个对象间的行为集中在一起。改变这些行为只需生成Mediator的子类即可，这样各个Colleague类可被复用。</div></li><li><div><span style="font-weight: bold;">将各Colleague解耦。</span>Mediator有利于各Colleague间的松耦合，你可以独立的该表和复用各Colleague类和Mediator类。</div></li><li><div><span style="font-weight: bold;">简化了对象协议。</span>用Mediator和各Colleague间的一对多交互来代替多对多交互。一对多的关系更易于理解、维护和扩展。将原本难以理解的网状结构转变为相对简单的星型结构。</div></li><li><div><span style="font-weight: bold;">对对象如何协作进行了抽象。</span>将中介作为一个独立的概念并将其封装在一个对象中，使你将注意力从对象各自本身的行为转移到它们之间的交互上来。这有助于弄清楚一个系统中的对象是如何交互的。由于把对象如何协作进行了抽象，将中介者作为一个独立的概念，并将其封装在一个对象中，这样关注的对象就从对象各自本身的行为转移到它们之间的交互上来，也就是站在一个更宏观的角度去看待系统。</div></li><li><div>通过将对象彼此解耦，可以增加对象的复用性。</div></li><li><div>通过将控制逻辑集中，可以简化系统维护。</div></li><li><div>可以让对象之间所传递的消息变得简单而且大幅减少。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div><span style="font-weight: bold;">使控制集中化。</span>中介者模式将交互的复杂性变为中介者的复杂性。因为中介者封装了协议，它可能变得比任一个Colleague都复杂。这可能使得中介者自身成为一个难于维护的庞然大物。如果设计不当，中介者对象会变得过于复杂。</div></li></ul><div><span style="font-weight: bold;">四、可能的改进</span></div><ul><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/mediator/sample01">通过注册的机制在中介者对象中动态的加入同事对象。</a>同事类可以在中介者模式当中进行注册，在注册的同时，也会被告知中介者对象。</div></li></ul><div><span style="font-weight: bold;">五、个人理解</span></div><ul><li><div>中介者模式比较简单，概念并不复杂，但一定注意不要滥用。</div></li><li><div>中介者模式在特定领域应用广泛。</div></li><li><div>可以参考SOA中的ESB理解，感觉ESB是改进版本的中介者模式。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br/></div><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="25493"/>
<h1>18.备忘录（Memento）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/9/13 8:15</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/11 0:54</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-weight: bold;">一、基本描述</span></div><ul><li><div>在不破坏封装性的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。</div></li><li><div>别名：Token</div></li><li><div>有时有必要记录一个对象的内部状态，为了允许用户取消不确定的操作或从错误中恢复过来，需要实现检查点和取消机制，而要实现这些机制，必须实现将状态保存在某处，这样才能将对象恢复到它们先前的状态。但是对象通常封装了其部分或所有的状态信息，使得其状态不能被其他对象访问，也就不可能在该对象之外保存其状态。而暴露其内部状态又将违反封装的原则，可能有损应用的可靠性和可扩展性。</div></li><li><div>可以使用备忘录模式解决这一问题。一个备忘录是一个对象，它存储另一个对象在某个瞬间的内部状态，而后者称为备忘录的原发器。当需要设置原发器的检查点时，取消操作机制会向原发器请求一个备忘录。原发器用描述当前状态的信息初始化该备忘录。只有原发器可以向备忘录中存取信息，备忘录对其他的对象“不可见”。</div></li><li><div>在以下情况下使用备忘录模式：</div></li></ul><div style="padding-left: 30px;">1）必须保存一个对象在某个时刻的（部分）状态，这样以后需要时它才能恢复到先前的状态。</div><div style="padding-left: 30px;">2）如果一个接口让其他对象直接得到这些状态，将会暴露对象的实现细节并破坏对象的封装性。</div><div style="padding-left: 30px;">3） 数据库连接的事务管理就是用的备忘录模式，如JDBC驱动使用备忘录模式来实现事务。</div><ul><li><div>备忘录模式比较适用于功能比较复杂的，但需要维护或记录属性历史的类，或者需要保存的属性只是众多属性中的一小部分时，Originator可以根据保存的Memento信息还原前一状态。如果在某个系统中使用命令模式时，需要实现命令的撤销功能，那么命令模式可以使用备忘录模式来存储可撤销操作的状态，有时一些对象的内部信息必须保存在对象以外的地方，但必须要由对象自己读取，这时，使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来，从而可以恰当的保持封装的边界。</div></li><li><div>备忘录模式最大的作用还是在当角色的状态改变的时候，有可能这个状态无效，此时就可以使用暂时存储起来的备忘录将状态复原这个作用。</div></li><li><div>通俗地说， 备忘录模式就是一个对象的备份模式， 提供了一种程序数据的备份方法。</div></li><li><div>由于备忘录模式有太多的变形和处理方式， 每种方式都有它自己的优点和缺点， 标准的<span style="font-size: unset; color: unset; font-family: unset;">备忘录模式很难在项目中遇到， 基本上都有一些变换处理方式。</span></div></li><li><div>不要在频繁建立备份的场景中使用备忘录模式， 一是<span style="font-size: unset; color: unset; font-family: unset;">控制不了备忘录建立的对象数量； 二是大对象的建立是要消耗资源的， 系统的性能需要考</span><span style="font-size: unset; color: unset; font-family: unset;">虑。</span></div></li><li><div>通过使用备忘录可以使系统恢复到某一个特定的历史状态。</div></li><li><div>备忘录模式提供了一种状态恢复的实现机制， 使得用户可以方便地回到一个特定的历史步<span style="font-size: unset; color: unset; font-family: unset;">骤， 当新的状态无效或者存在问题时， 可以使用暂时存储起来的备忘录将状态复原。</span></div></li><li><div>备忘录模式的核心是备忘录类（Memento）以及用于管理备忘录的管理者类的（Caretaker）设计。</div></li><li><div>备忘录模式在很多软件的使用过程中普遍存在， 但是在应用软件开发中， 它的使用频率并不<span style="font-size: unset; color: unset; font-family: unset;">太高， 因为现在很多基于窗体和浏览器的应用软件并没有提供撤销操作。 如果需要为软件提</span><span style="font-size: unset; color: unset; font-family: unset;">供撤销功能， 备忘录模式无疑是一种很好的解决方案。 在一些字处理软件、 图像编辑软件、</span><span style="font-size: unset; color: unset; font-family: unset;">数据库管理系统等软件中备忘录模式都得到了很好的应用。</span></div></li><li><div>当你需要让对象返回之前的状态时，就使用备忘录模式。</div></li><li><div>备忘录模式有两个目标：</div></li><ul><li><div>储存系统关键对象的重要状态</div></li><li><div>维护关键对象的封装。</div></li></ul></ul><div style="margin-left: 40px;">单一责任原则要求我们不要把保持状态的工作和关键对象混在一起，这个专门掌握状态的对象，就称为备忘录。<br/></div><div><span style="font-weight: bold;">二、图例</span></div><div><img src="0.设计模式专题_files/截图录屏_选择区域_20200914103954.png" type="image/png" data-filename="截图录屏_选择区域_20200914103954.png"/></div><ul><li><div><span style="font-weight: bold;">Memento（备忘录）：</span>备忘录存储原发器对象的内部状态。原发器根据需要决定备忘录存储原发器的哪些内部状态。防止原发器以外的其他对象访问备忘录。备忘录实际上有两个接口，管理者（caretaker）只能看到备忘录的窄接口——它只能将备忘录传递给其他对象。相反，原发器能够看到一个宽接口，允许它访问返回到先前状态所需的所有数据。理想的情况是只允许生成本备忘录的那个原发器访问本备忘录的内部状态。主要是在需要的时候提供发起人需要的内部状态。通常提供了与原发器相对应的属性（可以是全部，也可以是部分），用于存储原发器的状态。</div></li><li><div><span style="font-weight: bold;">Originator（原发器）：</span>原发器创建一个备忘录，用以记录当前时刻它的内部状态。使用备忘录恢复内部状态。负责定义哪些属于备份范围的状态。一般将需要保存内部状态的类设计为原发器。</div></li></ul><div style="padding-left: 30px;">原发器类中有三个成员：</div><div style="padding-left: 30px;">1. SetMemento(Memento m)：恢复备忘录，将Memento导入并将相关的数据恢复。</div><div style="padding-left: 30px;">2. CreateMemento()：创建备忘录，将当前需要保存的信息导入并实例化出一个Memento对象。</div><div style="padding-left: 30px;">3. state：需要保存的属性，可能有多个。</div><ul><li><div><span style="font-weight: bold;">Caretaker（管理者）：</span>负责保存好备忘录。不能对备忘录的内容进行操作或检查。对备忘录进行管理，保存和提供备忘录，只负责存储备忘录的对象，但不能对对象进行修改，也无需知道细节。在负责人类中，不应直接调用Memento中的状态改变方法，它的作用仅仅用于存储备忘录对象，将原发器备份生成的备忘录对象存储在其中，当用户需要恢复时，再将存储的备忘录对象取出。=&gt;只保存备忘录对象，不做其他的事。</div></li><li><div><span style="font-weight: bold;">协作：</span>管理者向原发器请求一个备忘录，保留一段时间后，将其送回给原发器，有时管理者不会将备忘录返回给原发器。因为原发器可能根本不需要退到先前的状态。备忘录是被动的，只有创建备忘录的原发器会对它的状态进行赋值和检索。</div></li></ul><div><span style="font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div><span style="font-weight: bold;">保持封装边界。</span>使用备忘录可以避免暴露一些只应由原发器管理却又必须存储在原发器之外的信息。该模式把可能很复杂的Originator内部信息对其他对象屏蔽起来，从而保持了封装边界。</div></li><li><div><span style="font-weight: bold;">简化了原发器。</span>在其他的保持封装性的设计中，Originator负责保持客户请求过的内部状态版本。这就把所有存储管理的重任交给了Originator。让客户管理请求的状态将会简化Originator，并且使得客户工作结束时无需通知原发器。</div></li><li><div>将被储存的状态放在外面，不要和关键对象混在一起，这可以帮助维护内聚。</div></li><li><div>保持关键对象的数据封装。</div></li><li><div>提供了容易实现的恢复能力。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div><span style="font-weight: bold;">使用备忘录可能代价很高。</span>如果原发器在生成备忘录时必须拷贝并存储大量的信息，或者客户非常频繁的创建备忘录和恢复原发器状态，可能会导致非常大的开销。除非封装和恢复Originator状态的开销不大，否则该模式可能并不合适。备忘录对象会非常消耗内存。</div></li><li><div><span style="font-weight: bold;">定义窄接口和宽接口。</span>在一些语言中可能难以保证只有原发器可访问备忘录的状态。</div></li><li><div><span style="font-weight: bold;">维护备忘录的潜在代价。</span>管理者负责删除它所维护的备忘录。然而，管理者不知道备忘录中有多少个状态，因此当存储备忘录时，一个本来很小的管理者可能会产生大量的存储开销。</div></li></ul><div><span style="font-weight: bold;">四、可能的改进</span></div><ul><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/memento/section6">使用Clone方式（原型模式）实现备忘录。</a></div></li><ul><li><div><span style="font-size: unset; color: unset; font-family: unset;">通过复制的方式产生一个对象的内部状态的备份。</span></div></li><li><div>发起人角色融合了发起人角色和备忘录角色，甚至可以融合管理者。</div></li><li><div>原型模式存在深拷贝和浅拷贝的问题， 在复杂的场景下它会 让你的程序逻辑异常混乱， 出现错误也很难跟踪。 因此Clone方式的备忘录模式适用于较简<span style="font-size: unset; color: unset; font-family: unset;">单的场景。</span></div></li><li><div>使用Clone方式的备忘录模式， 可以使用在比较简单的场景或者比较单一的场景<span style="font-size: unset; color: unset; font-family: unset;">中， 尽量不要与其他的对象产生严重的耦合关系。</span></div></li></ul><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/memento/section7">多状态的备忘录模式。</a></div></li><ul><li><div>备忘录模式的扩展，实现对象所有状态的备份与还原。</div></li><li><div>如果要设计一个在运行期决定备份状态的框架， 则建议采用AOP框架来实现， 避<span style="font-size: unset; color: unset; font-family: unset;">免采用动态代理无谓地增加程序逻辑复杂性。</span></div></li></ul><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/memento/section8">多备份的备忘录</a></div></li><ul><li><div>在管理员类中，将容纳备忘录的容器修改为Map类型即可。</div></li><li><div><span style="font-size: unset; color: unset; font-family: unset;">在系统设计时， 要严格限定备忘录的创建， 建议增加Map的上限， 否则系统很</span><span style="font-size: unset; color: unset; font-family: unset;">容易产生内存溢出情况。</span></div></li><li><div>可实现多次撤销：可对Map进行逆向遍历，回到一个指定的历史状态，还可以对集合进行正向遍历，实现重做操作。</div></li></ul><li><div>更好封装的备忘录（实现宽接口与窄接口的备忘录）</div></li><ul><li><div>双接口设计： 考虑对象的安全问题， 可以提供两个接口， 一个是业务的正常接口，<span style="font-size: unset; color: unset; font-family: unset;">实现必要的业务逻辑， 叫做宽接口； 另外一个接口是一个空接口， 什么方法都没有， 其目的</span><span style="font-size: unset; color: unset; font-family: unset;">是提供给子系统外的模块访问，叫做窄接口， 由于窄接口中没有提供任</span><span style="font-size: unset; color: unset; font-family: unset;">何操纵数据的方法， 因此相对来说比较安全。</span></div></li><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/memento/section9">Java中，使用内置类实现备忘录</a>：在<span style="font-size: unset; color: unset; font-family: unset;">原发器</span>类中建立一个内置类备忘录。</div></li><li><div>Java中，可以 通过将Memento类与Originator类定义在同一个包<span style="font-size: unset; color: unset; font-family: unset;">(package)中来实现封装：</span> 在Java语言中可使用默认访问标识符来定义Memento类， 即保证其包<span style="font-size: unset; color: unset; font-family: unset;">内可见。 将原发器和备忘录类放到同一个包中，让它们之间满足默认的包内可见性。</span></div></li><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Cplus/Memento/Memento_1">C++中，使用友元实现备忘录，让原发器类和备忘录类成为友元类</a>：Memento类中所有成员都做成私有的（private），Originator类作为Memento类的友元被声明。</div></li><li><div>在Java系统中，其实可以考虑使用序列化<span style="color: unset; font-family: unset; font-size: unset;">（ serialization）机制储存系统的状态。</span></div></li></ul></ul><div><br/></div><div><span style="font-weight: bold;">五、个人理解</span></div><ul><li><div>备忘录模式在理解与实现上并不复杂。</div></li><li><div>备忘录的实现与使用的语言有关。</div></li><li><div>备忘录的应用场景明确，功能比较专一，在应用时有相关场景很容易想到备忘录。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div></span>
</div>
<hr>
<a name="25720"/>
<h1>19.观察者（Observer）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/9/20 5:34</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2020/11/8 6:20</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-weight: bold;">一、基本描述</span></div><ul><li><div>定义对象间的一种一对多的依赖关系，当一个对象的状态发生改变时，所有依赖于它的对象都得到通知并被自动更新。</div></li><li><div>定义解释</div></li><ul><li><div>一对多的关系</div><div>利用观察者模式，主题是具有状态的对象，并且可以控制这些状态。也就是说，有“一个”具有状态的主题。另一方面，观察者使用这些状态，虽然这些状态并不属于它们。有许多的观察者，依赖主题来告诉它们状态何时改变了。这就产生一个关系：“一个”主题对“多个”观察者的关系。</div></li><li><div>依赖</div><div>因为主题是真正拥有数据的人，观察者是主题的依赖者，在数据变化时更新，这样比起让许多对象控制同一份数据来，可以得到更干净的OO设计。</div></li></ul><li><div>又称为：依赖（Dependents）、发布-订阅（Publish-Subscribe）</div></li><li><div>观察者模式可以帮你的对象知悉现状，不会错过该对象感兴趣的事，对象甚至在运行时可决定是否要继续被通知。</div></li><li><div>观察者模式提供了一种对象设计，让主题和观察者之间松耦合，以松耦合的方式在一系列对象之间沟通状态：</div></li><ul><li><div>关于观察者的一切，主题只知道观察者实现了某个接口（也就是Observer接口），主题不需要知道观察者的具体类是谁，做了些什么或其他任何细节。</div></li><li><div>任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表，所以我们可以随时增加观察者。事实上，在运行时我们可以用新的观察者取代现有的观察者，主题不会受到影响。同样的，也可以在任何时候删除某些观察者。</div></li><li><div>有新类型的观察者出现时，主题的代码不需要修改。假如我们有个新的具体类需要当观察者，我们不需要为了兼容新的类型而修改主题代码，所有要做的就是在新的类里实现此观察者的接口，然后注册为观察者即可。主题不在乎别的，它只会发送通知给所有实现了观察者接口的对象。</div></li><li><div>可以独立的复用主题或观察者。如果我们在其他地方需要使用主题或观察者，可以轻易的复用，因为二者并非紧耦合。</div></li><li><div>改变主题或观察者其中一方，并不会影响另一方。因为两者是松耦合的，所以只要他们之间的接口仍被遵守，我们就可以自由的改变它们。</div></li></ul><li><div>将一个系统分割成一系列相互协作的类有一个常见的副作用：需要维护相关对象间的一致性，我们不希望为了维持一致性而使各类紧密耦合，因为这样降低了其可复用性。</div></li><li><div>观察者模式描述了如何建立这种（上一条提到的）关系，这一模式中的关键对象是目标（subject）和观察者（observer）。一个目标可以有任意数目的依赖它的观察者。一旦目标的状态发生改变，所有的观察者都得到通知。作为对这个通知的相应，每个观察者都将查询目标以使其状态与目标的状态同步。</div></li><li><div>这种交互也称为发布-订阅，目标是通知的发布者，它发出通知时并不需要知道谁是它的观察者。可以有任意数目的观察者订阅并接收通知。</div></li><li><div>适用性：</div></li></ul><div style="margin-left: 40px;">1）一个抽象模型有两个方面，其中一个方面依赖于另一方面，将这二者封装在独立的对象中，以使它们可以各自独立的改变和复用。</div><div style="margin-left: 40px;">2）对一个对象的改变需要同时改变其他对象，而不知道具体有多少对象有待改变。</div><div style="margin-left: 40px;">3）一个对象必须通知其他对象，而它又不能假定其他对象是谁。换言之，不希望这些对象是紧密耦合的。</div><div style="margin-left: 40px;">4）跨系统的信息交换场景，如消息队列。</div><div style="margin-left: 40px;">5）需要在系统中创建一个触发链， A对象的行为将影响B对象， B对象的行为将影响C对<span style="font-size: unset; color: unset; font-family: unset;">象……， 可以使用观察者模式创建一种链式触发机制。（但是此触发链不宜过长，见《设计模式之禅》中的建议）</span></div><ul><li><div>观察多个目标。在某些情况下，一个观察者依赖于多个目标可能是由意义的。在这种情况下，必须扩展Update接口以使观察者知道是哪个目标送来的通知。目标对象可以简单的将自己作为Update操作的一个参数，让观察者知道应该去检查哪一个目标。</div></li><li><div>谁触发更新。目标和它的观察者依赖于通知机制来保持一致，但是到底哪一个对象调用Notify来触发更新，有两个选择：</div></li></ul><div style="margin-left: 40px;">1）由目标对象的状态设定操作在改变目标对象的状态后自动调用Notify。这种方法的优点是客户不需要记住要在目标对象上调用Notify，缺点是多个连续的操作会产生多次连续的更新，可能效率较低。</div><div style="margin-left: 40px;">2）让客户负责在适当的时候调用Notify。这样做的优点是客户可以在一系列的状态改变完成后再一次性的触发更新，避免了不必要的中间更新，缺点是给客户增加了触发更新的责任，由于客户可能会忘记调用Notify，这种方式容易出错。</div><ul><li><div>避免特定于观察者的更新协议——推/拉模型。</div></li></ul><div style="margin-left: 40px;">1）推模型（push model）：一个极端模型，目标向观察者发送关于改变的详细信息，而不管它们需要与否。</div><div style="margin-left: 40px;">2）拉模型（pull model）：一个极端模型，目标除最小通知外什么也送不出，而在此之后由观察者显式的向目标询问细节。</div><div style="margin-left: 40px;">3）拉模型强调的是目标不知道它的观察者，而推模型假定目标知道一些观察者的需要的信息。推模型可能使得观察者相对难以复用，因为目标对观察者的假定可能并不总是正确的。另一方面，拉模型可能效率较差，因为观察者对象需在没有目标对象帮助的情况下确定什么改变了。</div><ul><li><div>显式的指定感兴趣的改变。可以扩展目标的注册接口，让各观察者注册为仅对特定事件感兴趣，以提高更新的效率。当一个事件发生时，目标仅通知那些已注册为对该事件感兴趣的观察者。支持这种做法的一种途径是，使用目标对象的<span style="font-weight: bold;">方面（aspect）</span>的概念。可用如下代码将观察者对象注册为对目标对象的某特定事件感兴趣：</div></li></ul><div>            void Subject::Attach(Observer*, Aspect&amp; interest);</div><div>            此处interest指定感兴趣的事件。在通知时刻，目标将这方面的改变作为Update操作的一个参数提供给它的观察者，例如：</div><div>            void Observer::Update(Subject*, Aspect&amp; interest);</div><div>            个人理解，这里的方面指的其实是对象当中的一部分成员，比如上例中，方面就是目标对象当中的部分事件（成员变量）。</div><ul><li><div>观察者模式所做的工作其实就是在解除耦合，让耦合的双方都依赖于抽象，而不是依赖于具体，从而使得各自的变化都不会影响另一边的变化。</div></li><li><div>观察者模式在Java语言中地位非常重要，在JDK的java.util包中，针对观察者模式提供了两个基础设施：</div></li><ul><li><div>java.util.Observable：可扩展父类，对应于Subject；</div></li><li><div>java.util.Observer：接口，对应Observer。</div></li></ul></ul><div style="margin-left: 40px;">这两个类使用方法如下：</div><ul><ul><li><div>可以使用推或者拉的方式传递数据</div></li><li><div>Observable 提供 addObserver() 和 deleteObserver() 方法，增加和删除观察者</div></li><li><div>发送通知的步骤：</div></li><ul><li><div>在可观察者类中，先调用 setChanged() 方法，标记状态已经改变的事实</div></li><li><div>调用两种 notifyObserver() 方法中的一个：</div></li><ul><li><div>notifyObservers()</div></li><li><div>notifyObservers(Object arg)：其中，arg是一个数据对象</div></li></ul></ul><li><div>接收通知：</div></li><ul><li><div>在 Observer 接口中，实现了更新方法：</div><div>update(Observable o, Object arg)</div><div>主题本身当作第一个变量，好让观察者知道是哪个主题通知它的</div><div>arg是上述 notifyObserver(Object arg) 的参数，是一个数据对象，用于推送数据，如果没有说明则为空</div></li></ul><li><div>如果希望“推”数据给观察者，可以把数据对象传送给 notifyObservers(Object arg) 方法，否则，观察者就必须从可观察者对象中“拉”数据</div></li><li><div>setChanged() 方法用来标记状态已经改变的事实，好让 notifyObservers() 知道当它被调用时应该更新观察者。如果调用 notifyObserver() 之前没有先调用 setChanged()，观察者就不会被通知。</div><div>这样做有其必要性，setChanged() 方法可以让你在更新观察者时，有更多的弹性，可以更适当的通知观察者。</div><div>相关的方法有：</div></li><ul><li><div>setChanged()：设置 changed 状态标志位true</div></li><li><div>clearChanged()：将 changed 状态设置回false</div></li><li><div>hasChanged()：获取 changed 标志的当前状态</div></li></ul></ul><li><div>观察者模式是使用频率最高的设计模式之一。它为实现对象之间的联动提供了一套完整的解决方案， 凡是涉及到<span style="font-size: unset; color: unset; font-family: unset;">一对一或者一对多的对象交互场景都可以使用观察者模式。</span></div></li><li><div>观察者模式描述了如何建立对象与对象之间的依赖关系，以及如何构造满足这种需求的系统。</div></li><li><div>在观察者模式当中，代码不应当依赖主题通知观察者对象的顺序（特定的通知顺序），因为一旦观察者/主题的实现有所改变，通知次序就会改变，很可能会产生错误的结果。</div></li><li><div>对设计原则的支持</div></li><ul><li><div>找出程序中会变化的方面，然后将其和固定不变的方面相分离</div><div>在观察者模式中，会改变的是主题的状态，以及观察者的数目和类型，用这个模式可以改变依赖于主题状态的对象，却不必改变主题，这就叫提前规划。</div></li><li><div>依赖倒转原则：针对接口编程，不针对实现编程</div><div>主题与观察者都使用接口，观察者利用主题的接口向主题注册，而主题利用观察者接口通知观察者。这样可以让两者之间运作正常，又同时具有松耦合的优点。</div></li><li><div>合成复用原则：多用组合，少用继承</div><div>观察者模式利用“组合”将许多观察者组合进主题类中。对象之间的这种关系不是通过继承产生的，而是在运行时利用组合的方式而产生的。</div></li></ul><li><div>一些实际应用：</div></li><ul><li><div>在JDK中，事件处理模型采用基于观察者模式的委派事件模型（Delegation Event Model，DEM），即一个Java组件所引发的事件并不由引发事件的对象自己来负责处理，而是委派给独立的事件处理对象负责。</div></li><li><div>在当前流行的MVC(Model-View-Controller)架构中也应用了观察者模式， MVC是一种架构模<span style="font-size: unset; color: unset; font-family: unset;">式， 它包含三个角色： 模型(Model)， 视图(View)和控制器(Controller)。 其中模型可对应于观察</span><span style="font-size: unset; color: unset; font-family: unset;">者模式中的观察目标， 而视图对应于观察者， 控制器可充当两者之间的中介者。 当模型层的</span><span style="font-size: unset; color: unset; font-family: unset;">数据发生改变时， 视图层将自动改变其显示内容。</span></div></li><li><div>Java当中，Swing大量使用观察者模式，许多GUI框架也是如此。</div></li><li><div>在JavaBeans和RMI当中也可以被应用。</div></li></ul><li><div>观察者模式的应用：</div></li><ul><li><div>各种编程语言的GUI事件处理</div></li><li><div>基于事件的XML解析技术（ 如SAX2）</div></li><li><div>Web事件处理</div></li><li><div>观察者模式是JDK中使用最多的模式之一，非常有用。</div></li></ul></ul><div><span style="font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="0.设计模式专题_files/Image [48].png" type="image/png" data-filename="Image.png"/></div><ul><li><div><span style="font-weight: bold;">Subject（目标）：</span>目标知道它的观察者，可以有任意多个观察者观察同一个目标。提供注册和删除观察者对象的接口，这里的对象是观察者基类对象。也称为主题，抽象通知者，用一个抽象类或一个接口实现。定义目标必须实现的职责，仅仅完成作为目标必须实现的职责：管理观察者并通知观察者（也就是说最好不要涉及具体业务）。职责非常简单，就是定义谁能够观察，谁不能够观察。把所有对观察者对象的引用保存在一个聚类里，每个目标都可以有任何数量的观察者，抽象主题提供一个接口，可以增加和删除观察者对象。</div></li><li><div><span style="font-weight: bold;">Observer（观察者）：</span>为那些在目标发生改变时需获得通知的对象定义一个更新接口<span style="font-size: unset; color: unset; font-family: unset;">。抽象观察者，为所有的具体观察者类定义一个接口，在得到主题的通知时更新自己。接收到消息后调用update操作，对接收到的信息进行处理。</span></div></li><li><div><span style="font-weight: bold;">ConcreteSubject（具体目标）：</span>将有关状态存入各ConcreteObserver对象，当它的状态发生改变时，向它的各个观察者发出通知。定义自己的业务逻辑，同时定义对那些事件进行通知，通常包含有经常发生改变的成员，当它发生改变时，向各个观察者发出通知，如无需扩展目标类，具体目标类可以省略。</div></li><li><div><span style="font-weight: bold;">ConcreteObserver（具体观察者）：</span>维护一个指向ConcreteSubject对象的引用。存储有关状态，这些状态应与目标的状态保持一致。实现Observer的更新接口以使自身状态与目标状态保持一致。这里的对象是具体的目标对象，每个观察者接收消息后有自己的处理逻辑。</div></li><li><div><span style="font-weight: bold;">协作：</span></div></li></ul><div style="margin-left: 40px;">1）当ConcreteSubject发生任何可能导致其观察者与其本身状态不一致的改变时，它将通知它的各个观察者。</div><div style="margin-left: 40px;">2）在得到一个具体目标的改变通知后，ConcreteObserver对象可向目标对象查询信息，ConcreteObserver使用这些信息以使它的状态与目标对象的状态一致。</div><div style="margin-left: 40px;">3）有多个Observer对象，其中一个Observer对象向ConcreteSubject对象发出改变状态的请求，需要注意发出改变请求的Observer对象本身状态并不立即更新，而是将状态改变推迟到它从目标得到一个通知之后。</div><div style="margin-left: 40px;">4）通知notify不总是由目标对象调用，也可以被一个观察者或其他对象调用。</div><div><span style="font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div><span style="font-size: unset; color: unset; font-family: unset; font-weight: bold;">目标和观察者之间的抽象耦合</span><span style="font-size: unset; color: unset; font-family: unset;">。一个目标所知道的仅仅是它由一系列观察者，每个都符合抽象的Observer类的简单接口。目标不知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间的耦合是抽象的和最小的。</span><span style="font-size: unset; color: unset; font-family: unset;">观察者模式允许独立的改变目标和观察者。可以单独复用目标对象而无需同时复用其观察者，反之亦然。它也使你可以在不改动目标和其他观察者的前提下增加观察者。</span>由于观察目标和观察者没有紧密地耦合在一起， 因<span style="font-size: unset; color: unset; font-family: unset;">此它们可以属于不同的抽象化层次。</span></div></li><li><div><span style="font-weight: bold;">支持广播通信</span>。不像通常的请求，目标发送的通知不需指定它的接收者。通知被自动广播给所有已向该目标登记的有关对象。目标对象并不关心到底有多少对象对自己感兴趣，它唯一的责任就是通知它的各观察者。这给了在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。</div></li><li><div><span style="font-weight: bold;">建立一套触发机制</span>。根据单一职责原则，每个类的职责是单一的，观察者模式可以完美的实现这了的触发链，将各个职责单一的职责链串联成复杂的逻辑关系。</div></li><li><div>观察者模式可以实现表示层和数据逻辑层的分离， 定义了稳定的消息更新传递机制， 并抽<span style="font-size: unset; color: unset; font-family: unset;">象了更新接口， 使得可以有各种各样不同的表示层充当具体观察者角色。</span></div></li><li><div>观察者模式满足“开闭原则”的要求， 增加新的具体观察者无须修改原有系统代码， 在具体<span style="font-size: unset; color: unset; font-family: unset;">观察者与观察目标之间不存在关联关系的情况下， 增加新的观察目标也很方便。</span><span style="font-size: unset; color: unset; font-family: unset;">无论观察者还是目标都非常容易扩展。</span></div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div><span style="font-size: unset; color: unset; font-family: unset;"><span style="font-size: unset; color: unset; font-family: unset; font-weight: bold;">意外的更新</span>。因为一个观察者并不知道其他观察者的存在，它可能对改变目标的最终代价一无所知。在目标上一个看似无害的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外，如果依赖准则的定义或维护不当，常常会引起错误的更新，这种错误通常很难捕捉。简单的更新协议不提供具体细节说明目标中什么被改变了，这就使得上述问题更加严重，如果没有其他协议帮助观察者发现什么发生了改变，它们可能会被迫尽力减少改变。</span></div></li><li><div>尽管已经使用了依赖倒转原则，但是“抽象通知者”还是依赖“抽象观察者”，也就是说，万一没有了抽象观察者这样的接口，通知的功能就完成不了。</div></li><li><div><span style="font-weight: bold;">需要考虑开发效率</span>。一个目标，多个观察者，开发和调试比较复杂。</div></li><li><div><span style="font-weight: bold;">需要考虑运行效率</span>。如果一个观察目标对象有很多直接和间接的观察者，将所有的观察者都通知到会花费很多时间。并且应考虑异步通知的方式，否则如果通知顺序执行，一个观察者卡克，会影响整体运行效率。多级触发时的效率更令人担忧。</div></li><li><div>如果在观察者和观察目标之间存在循环依赖，观察目标可能会触发它们之间进行循环调用，可能导致系统崩溃。</div></li></ul><div><span style="font-weight: bold;">四、可能的改进</span></div><ul><li><div>可以利用委托改进观察者模式。</div></li><li><div>观察者和目标之间的消息沟通。目标状态改变会触发观察者的一个行为，同时会传递一个消息给观察者。实际中，观察者的Update方法接受两个参数：一个是目标对象，一个是DTO（Data Transfer Object，数据传输对象）</div></li><li><div>需要解决观察者如何快速响应的问题：一是采用多线程技术的异步通信架构，二是采用缓存技术。</div></li></ul><div><span style="font-weight: bold;">五、个人理解</span></div><ul><li><div>观察者模式的思路及实现并不难，在实际中应用非常广泛，实际上其思路在宏观的架构设计上也有很多应用。</div></li><li><div>核心思路是解耦。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div></div><div><br/></div></span>
</div>
<hr>
<a name="26168"/>
<h1>20.状态（State）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/9/30 7:24</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/27 6:30</i></td></tr>
<tr><td><b>作者：</b></td><td><i>YuGe</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 12pt; font-weight: bold;">一、基本描述</span></div><ul><li><div><span style="font-size: 16px;">允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。</span></div></li><li><div><span style="font-size: 16px;">又叫做状态对象（object for state）</span></div></li><li><div><span style="font-size: 16px;">对于定义的理解：</span></div></li><ul><li><div><span style="font-size: 16px;">允许一个对象在其内部状态改变时改变它的行为</span></div><div><span style="font-size: 16px;">因为这个模式将状态封装成为独立的类，并将动作委托到代表当前状态的对象，此时行为会随着内部状态而改变</span></div></li><li><div><span style="font-size: 16px;">对象看起来似乎修改了它的类</span></div><div><span style="font-size: 16px;">从客户的视角来看：如果说你使用的对象能够完全改变它的行为，那么你会觉得，这个对象实际上是从别的类实例化而来的。然而，实际上，我们是在使用组合通过简单引用不同的状态对象来造成类改变的假象。</span></div></li></ul><li><div><span style="font-size: 16px;">适用性：</span></div></li><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">行为随状态改变而改变的场景。</span></span><span style="font-size: 16px;">一个对象的行为取决于它的状态，并且它必须在运行时刻根据状态改变它的行为。</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">条件、分支语句的替代者。</span>一个操作中含有庞大的多分支条件语句，且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常，有多个操作包含这一相同的条件结构。状态模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象，这一对象可以不依赖于其他对象而独立变化。（实际上就是将原本的一大堆的 if-else 语句换成了 state 模式，增加了扩展性和可维护性）</span></div></li></ul><li><div><span style="font-size: 16px;">对于实现的考虑：</span></div></li><ul><li><div><span style="font-size: 16px; font-weight: bold;">State 模式不指定哪一个参与者定义状态转换规则。</span></div></li><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">统一由环境类来负责状态之间的转换【</span></span><span style="font-size: 16px;"><span style="font-size: 16px; color: unset; font-family: unset; font-style: italic;">Context </span></span><span style="font-size: 16px; color: unset; font-family: unset; font-style: italic;">对象切换 ConcreteState 类</span><span style="font-size: 16px; font-weight: bold;">】</span><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">。</span>如果该规则是固定的，那么它们可在 Context 中完全实现。</span> <span style="font-size: 12pt;">此时，环境类还充当了状态管理器（State Manager</span><span style="font-size: 12pt;">）</span><span style="font-size: 12pt;">角</span><span style="font-size: 12pt; color: unset; font-family: unset;">色， 在环境类的业务方法中通过对某些属性值的判断实现状态转换， 还可以提供一个专门的</span><span style="font-size: 12pt; color: unset; font-family: unset;">方法用于实现属性判断和状态转换。</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">由具体的状态类来负责状态之间的转换【</span></span><span style="font-size: 16px; color: unset; font-family: unset; font-style: italic;">State 对象自身切换类</span><span style="font-size: 16px; font-weight: bold;">】</span><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">。</span></span><span style="font-size: 12pt;">可以在具体状态类的业务方法中判断环境类的某些</span><span style="font-size: 12pt; color: unset; font-family: unset;">属性值再根据情况为环境类设置新的状态对象，实现状态转换，同样，也可以提供一个专门</span><span style="font-size: 12pt; color: unset; font-family: unset;">的方法来负责属性值的判断和状态转换<span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">。</span></span><span style="font-size: 16px; color: unset; font-family: unset;">这需要 Context 增加一个接口，让 State 对象显式的设定 Context 的当前状态。</span></div></li></ul></ul></ul><div style="margin-left: 120px;"><span style="font-size: 16px; color: unset; font-family: unset;">若让 State 子类自身指定它们的后继状态以及何时进行转换，通常更灵活、更合适。</span><span style="font-size: 12pt; color: unset; font-family: unset;">用这种方法分散转换逻辑可以很容易地定义新的 State 子类来修改和扩展该逻辑。</span></div><div style="margin-left: 120px;"><span style="font-size: 12pt; color: unset; font-family: unset;">这样做的一个缺点是，一个 State 子类至少拥有一个其他子类的信息，这就在各子类之间产生了实现依赖。同时，</span><span style="font-size: 12pt;">状态类与环境类之间就将存在依赖或关联关</span><span style="font-size: 12pt; color: unset; font-family: unset;">系，因为状态类需要访问环境类中的属性值。</span></div><ul><ul><li><div><span style="font-size: 16px; font-weight: bold;">创建和销毁 State 对象的时机。</span><span style="font-size: 16px;">需要考量究竟是仅当需要 State 对象时才创建它们并随后销毁它们（</span><span style="font-size: 16px; font-style: italic;">转换到本状态的时候临时创建，转换到别的状态的时候销毁</span><span style="font-size: 16px;">），还是提前创建它们并且始终不销毁它们（</span><span style="font-size: 16px; font-style: italic;">在构造函数当中或者全局初始化所有的状态对象，状态对象一直不销毁，生命周期与程序的生命周期相同</span><span style="font-size: 16px;">）。</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">当将要进入的状态在运行时是不可知的，并且上下文不经常改变状态时，第一种选择较为可取。这种方法避兔创建不会被用到的对象，如果 State 对象存储大量的信息时这一点很重要。当状态改变很频繁时，第二种方法较好。在这种情况下最好避免销毁状态，因为可能很快再次需要用到它们。此时可以预先一次付清创建各个状态对象的开销，并且在运行过程中根本不存在销毁状态对象的开销。但是这种方法可能不太方便，因为 Context 必须保存对所有可能会进入的那些状态的引用。（</span><span style="font-size: 12pt; font-style: italic;">实际上是一个时空权衡问题</span><span style="font-size: 12pt;">）</span></div><ul><li><div><span style="font-size: 16px;">状态对象通常是单例。</span></div></li><li><div><span style="font-size: 16px;">状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况，把状态的判断逻辑转移到表示不同状态的一系列类当中，可以把复杂的判断逻辑简化。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 16px;">如果方法很长，且有很多判断分支，意味着它责任过大，是坏代码的味道。面向对象设计其实就是希望做到代码的责任分解，需要将这些分支变成多个类，增加时不影响其他的类，然后状态变化在各自的类中完成。</span></div><ul><li><div><span style="font-size: 16px;">状态模式的目的是为了消除庞大的条件分支语句，大的分支判断会使得它们难以修改和扩展，状态模式通过把各种状态转移逻辑分布到 State 的子类之间，来减少相互间的依赖。</span></div></li><li><div><span style="font-size: 16px;">对于每一个状态类，需要实现在一个状态下的两个任务模型：这个状态是如何产生的，在这个状态下还能做什么其他动作（即这个状态怎么过渡到其他状态）。</span></div></li><li><div><span style="font-size: 12pt;">状态模式的核心是封装， 状态的变更引起了行为的变更， 从外部看起来就好像这个对象</span><span style="font-size: 12pt; color: unset; font-family: unset;">对应的类发生了改变一样。</span></div></li><li><div><span style="font-size: 12pt;">状态模式相对来说比较复杂， 它提供了一种对物质运动的另一个观察视角， 通过状态变</span><span style="font-size: 12pt; color: unset; font-family: unset;">更促使行为的变化。</span></div></li><li><div><span style="font-size: 12pt;">状态模式隐藏了状态的变化过程，它的切换引起了行为的变化。对外来说，</span><span style="font-size: 12pt; color: unset; font-family: unset;">只看到行为的发生改变，而不用知道是状态变化引起的。</span></div></li><li><div><span style="font-size: 16px;">注意事项：</span> <span style="font-size: 12pt;">状态模式适用于当某个对象在它的状态发生改变时， 它的行为也随着发生比较大的变</span><span style="font-size: 12pt; color: unset; font-family: unset;">化， 也就是说在行为受状态约束的情况下可以使用状态模式， 而且使用时对象的状态最好不</span><span style="font-size: 12pt; color: unset; font-family: unset;">要超过5个。</span></div></li><li><div><span style="font-size: 16px;">状态模式用于描述对象状态及其转换。</span></div></li><li><div><span style="font-size: 12pt;">在状态模式中， 我们将对象在每一个状态下的</span><span style="font-size: 12pt; color: unset; font-family: unset;">行为和状态转移语句封装在一个个状态类中， 通过这些状态类来分散冗长的条件转移语句，</span><span style="font-size: 12pt; color: unset; font-family: unset;">让系统具有更好的灵活性和可扩展性。</span></div></li><li><div><span style="font-size: 12pt;">状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。 当系统中某</span><span style="font-size: 12pt; color: unset; font-family: unset;">个对象存在多个状态， 这些状态之间可以进行转换， 而且对象在不同状态下行为不相同时可</span><span style="font-size: 12pt; color: unset; font-family: unset;">以使用状态模式。 状态模式将一个对象的状态从该对象中分离出来， 封装到专门的状态类</span><span style="font-size: 12pt; color: unset; font-family: unset;">中， 使得对象状态可以灵活变化， 对于客户端而言， 无须关心对象状态的转换以及对象所处</span><span style="font-size: 12pt; color: unset; font-family: unset;">的当前状态， 无论对于何种状态的对象， 客户端都可以一致处理。</span></div></li><li><div><span style="font-size: 12pt;">状态模式将一个对象在不同状态下的不同行为封装在一个个状态类中，通过设置不同的状态</span><span style="font-size: 12pt; color: unset; font-family: unset;">对象可以让环境对象拥有不同的行为，而状态转换的细节对于客户端而言是透明的，方便了</span><span style="font-size: 12pt; color: unset; font-family: unset;">客户端的使用。</span></div></li><li><div><span style="font-size: 16px;">遵守“封装变化”原则：局部化每个状态的行为，这样一来，如果针对某个状态做了改变，就不会把其他的代码搞乱了。</span></div></li><li><div><span style="font-size: 16px;">实际应用：</span></div></li><ul><li><div><span style="font-size: 16px;">实际中的状态模式，大多均需要实现状态间的自由切换。</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">【</span><span style="font-size: 12pt; font-style: italic;">初步理解实现状态之间自由切换需要在 State 抽象类中合理设置接口：每当有一个新的状态，就需要在 State 当中有一个新的接口与之对应，这一点在《GOF》的TCP实例和《设计模式之禅》中的示例中有体现，也就是说在 State 中有全量的接口，而在 ConcreteState 中只实现（重载）与之相关的接口，其余接口沿用 State 类中的默认实现（即无关接口仍然在 ConcreteState 当中存在，但不会有行为及状态转换）</span><span style="font-size: 12pt;">】</span></div><ul><ul><li><div><span style="font-size: 12pt;">在有些情况下， 多个环境对象可能需要共享同一个状态， 如果希望在系统中实现多个环境对</span><span style="font-size: 12pt; color: unset; font-family: unset;">象共享一个或多个状态对象， 那么需要将这些状态对象定义为环境类的静态成员对象。【</span><span style="font-size: 12pt; color: unset; font-family: unset; font-style: italic;">多个环境对象是同一个 Context 类的不同实例</span><span style="font-size: 12pt; color: unset; font-family: unset;">】【</span><span style="font-size: 12pt; color: unset; font-family: unset; font-style: italic;">另外这种情况也可以结合 Flyweight 享元模式混合实现</span><span style="font-size: 12pt; color: unset; font-family: unset;">】</span></div></li><li><div><span style="font-size: 16px;">状态模式在工作流和游戏开发中都得到了广泛的应用，工作流开发中的状态机管理，实际上就是状态模式中的 Context 类的升级，公文状态的转换和游戏中角色的升级也使用到了状态模式。</span></div></li></ul></ul><div><span style="font-size: 12pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="0.设计模式专题_files/Image [49].png" type="image/png" data-filename="Image.png"/></div><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">Context（环境）</span>：又称为上下文类。定义客户感兴趣的接口。维护一个 ConcreteState 子类的实例，这个实例定义当前状态。客户实际上使用此类，负责具体状态的切换，作用是串联各个状态的过渡。环境类实际上是真正拥有状态的对象，我们只是将环境类中与状态有关的代码提取出来封装到专门的状态类中。作为 Context 类有两个约束：</span></div></li><ul><li><div><span style="font-size: 16px;">把状态对象声明为静态常量，有几个状态对象就声明几个静态常量。</span></div></li><li><div><span style="font-size: 16px;">环境角色具有状态抽象角色定义的所有行为，具体执行使用委托方式。</span></div></li></ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">State（状态）</span>：定义一个接口以封装与 Context 的一个特定状态相关的行为。对各状态下的共有行为进行封装。接口或抽象类，负责对象状态的定义，并且封装环境角色以实现状态切换。</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">ConcreteState subclasses（具体状态子类）</span>：每一个子类实现一个与 Context 的一个状态相关的行为。有两个职责：</span></div></li><ul><li><div><span style="font-size: 16px;">本状态行为的管理（</span><span style="font-size: 16px; font-style: italic;">本状态下要做的事</span><span style="font-size: 16px;">）。</span></div></li><li><div><span style="font-size: 16px;">趋向状态的处理（</span><span style="font-size: 16px; font-style: italic;">本状态如何过渡到其他状态</span><span style="font-size: 16px;">）。</span></div></li></ul><li><div><span style="font-size: 12pt; font-weight: bold;">协作：</span></div></li><ul><li><div><span style="font-size: 16px;">Context 将与状态相关的请求委托给当前的 ConcreteState 对象处理。</span></div></li><li><div><span style="font-size: 16px;">Context 可将自身作为一个参数传递给处理该请求的状态对象，这使得状态对象在必要时可访问 Context。</span></div></li><li><div><span style="font-size: 16px;">Context 是客户使用的主要接口。客户可用状态对象来配置一个 Context，一旦一个 Context 配置完毕，它的客户不再需要直接与状态对象打交道。</span></div></li><li><div><span style="font-size: 12pt;">Context 与 ConcreteState 子类都可决定哪个状态是另外一个的后继者，以及是在何种条件下进行状态转换。（</span><span style="font-size: 12pt; font-style: italic;">实际上就是一个有限状态机</span><span style="font-size: 12pt;">）</span></div></li><li><div><span style="font-size: 16px;">Context和C</span><span style="font-size: 16px; color: unset; font-family: unset;">oncreteState都可以决定状态转换的流向</span></div><div><span style="font-size: 16px;">当状态转换时固定的时候，就适合放在Context中，然而，当转换时更动态的时候，通常会放在状态类中。</span></div><div><span style="font-size: 16px;">将状态转换放在状态类中的缺点是：状态类之间产生了依赖。</span></div><div><span style="font-size: 16px;">做这个决策的同时，也等于是在为另一件事情做决策：当系统进化时，究竟哪个类是对修改封闭（Context还是ConcreteState）的。</span></div></li></ul></ul><div><span style="font-size: 12pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-size: 12pt; font-weight: bold;">优点</span></div><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">将与特定状态相关的行为局部化，并且将不同状态的行为分割开来。</span>State 模式将所有与一个特定的状态相关的行为都放入一个对象中。因为所有与状态相关的代码都存在于某一个 State 子类中，所以通过定义新的子类可以很容易的增加新的状态和转换。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt;">另一个方法是使用数据值定义内部状态并且让 Context 操作来显式地检查这些数据。但这样将会使整个 Context 的实现中遍布看起来很相似的条件语句或 case 语句。增加一个新的状态可能需要改变若干个操作，这就使得维护变得复杂了。</span></div><div style="margin-left: 40px;"><span style="font-size: 16px;">State 模式避免了这个问题，但可能会引入另一个问题，因为该模式将不同状态的行为分布在多个 State 子类中。这就增加了子类的数目，相对于单个类的实现来说不够紧凑。但是如果有许多状态时这样的分布实际上更好一些，否则需要使用巨大的条件语句。</span></div><div style="margin-left: 40px;"><span style="font-size: 12pt;">正如很长的过程一样，巨大的条件语句是不受欢迎的。它们形成一大整并且使得代码不够清晰，这又使得它们难以修改和扩展。 State 模式提供了一个更好的方法来组织与特定状态相关的代码。决定状态转移的逻辑不在单块的 if 或  switch 语句中，而是分布在 State 子类之间。</span><span style="font-size: 12pt; color: unset; font-family: unset;">将每一个状态转换和动作封装到一个类中，就把着眼点从执行状态提高到整个对象的状态。</span><span style="font-size: 12pt; color: unset; font-family: unset;">这将使代码结构化并使其意图更加清晰。</span></div><div style="margin-left: 40px;"><span style="font-size: 16px;">同时，</span><span style="font-size: 12pt;">状态变换放置到类的内部来实现，外部的调用不用知道类</span><span style="font-size: 12pt; color: unset; font-family: unset;">内部如何实现状态和行为的变换。</span></div><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">使得状态转换显示化</span>。当一个对象</span><span style="font-size: 12pt;">仅以内部数据值来定义当前状态时，其状态仅表现为对一些变量的赋值，这不够明确。为不同的状态引入独立的对象使得转换变得更加明确。</span><span style="font-size: 12pt; color: unset; font-family: unset;">而且， State 对象可保证 Context 不会发生内部状态不一致的情况，因为从 Context 的角度看，状态转换是原子的 —— 只需重新绑定一个变量（即 Context 的 State 对象变量），而无需为多个变量赋值。</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">State 对象可被共享</span>。</span><span style="font-size: 12pt;">如果 State 对象没有实例变量 —— 它们表示的状态完全以它们的类型来编码 —— 那么各 Context 对象可以共享一个 State 对象。当状态以这种方式被共享时，它们必然是没有内部状态只有行为的轻量级对象（可参考 Flyweight 模式）。（</span><span style="font-size: 12pt; font-style: italic;">这一条就是说各子 ConcreteState 可以没有内部成员，只有方法，其类就是状态的代表</span><span style="font-size: 12pt;">）</span></div></li><li><div><span style="font-size: 16px;">很好的体现了</span><span style="font-size: 12pt; font-weight: bold;">单一职责原则</span><span style="font-size: 12pt;">，每个状态都是一个子类。</span></div></li><li><div><span style="font-size: 12pt;">可以让多个环境对象共享一个状态对象，从而减少系统中对象的个数。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">缺点</span></div><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">子类可能太多，可能发生类膨胀。</span>一个事物可能有很多个状态，完全使用状态模式会有很多子类，不好管理，导致系统运行开销增大。应在项目中做实际的权衡。</span></div></li><li><div><span style="font-size: 12pt;">状态模式的结构与实现都较为复杂，如果使用不当将导致程序结构和代码的混乱，增加系</span><span style="font-size: 12pt; color: unset; font-family: unset;">统设计的难度。</span></div></li><li><div><span style="font-size: 12pt;">状态模式对</span><span style="font-size: 12pt; font-weight: bold;">开闭原则</span><span style="font-size: 12pt;">的支持并不太好，</span><span style="font-size: 12pt;">如要增加状态就要增</span><span style="font-size: 12pt; color: unset; font-family: unset;">加子类，</span><span style="font-size: 12pt;">增加新的状态类需要修改那些负责状态转换的源</span><span style="font-size: 12pt; color: unset; font-family: unset;">代码，否则无法转换到新增状态；而且修改某个状态类的行为也需修改对应类的源代码。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt;">【<span style="font-size: 12pt; font-style: italic;">关于对开闭原则的支持，《设计模式之禅》和《Design Pattern Java》两本书描述的不一样，</span></span><span style="font-size: 12pt; font-style: italic;">《设计模式之禅》认为状态模式支持开闭原则，这是优点，但是</span><span style="font-size: 12pt; font-style: italic;">《Design Pattern Java》认为状态模式对开闭原则支持的不够好，这是缺点。这里采用</span><span style="font-size: 12pt; font-style: italic;">《Design Pattern Java》当中的描述</span><span style="font-size: 12pt;">】</span></div><div><span style="font-size: 12pt; font-weight: bold;">四、可能的改进</span></div><ul><li><div><span style="font-size: 16px;">结合生成器模式（Builder）会起到非常好的封装作用。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">五、个人理解</span></div><ul><li><div><span style="font-size: 16px;">状态模式的概念与实现相对而言比较复杂</span></div></li><li><div><span style="font-size: 16px;">状态模式的应用似乎很广泛，在实际中有很多状态相关的编码或各种条件语句的长代码可用状态模式实现。</span></div></li><li><div><span style="font-size: 12pt;">状态模式的应用场景认识比较统一：1）状态相关的场景，或者2）包含大量条件语句的场景（但是包含大量条件语句的场景也可以用策略模式替代，所以还是要认准状态相关的场景）</span></div></li><li><div><span style="font-size: 16px;">状态模式和策略模式的区别：个人认为状态模式和策略模式非常相似，最主要的区别大概是状态模式中要关心状态的转换，而在策略模式中，需要关心策略的选用（不用转换）</span></div></li></ul><div><font style="font-size: 12pt;"><br clear="none"/></font></div><div><font style="font-size: 12pt;"><br clear="none"/></font></div><div><font style="font-size: 12pt;"><br clear="none"/></font></div><div><font style="font-size: 12pt;"><br clear="none"/></font></div><div><font style="font-size: 12pt;"><br clear="none"/></font></div><div><font style="font-size: 12pt;"><br clear="none"/></font></div><div><font style="font-size: 12pt;"><br clear="none"/></font></div><div><font style="font-size: 12pt;"><br clear="none"/></font></div><div><font style="font-size: 12pt;"><br clear="none"/></font></div><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="26414"/>
<h1>21.策略（Strategy）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/10/4 6:07</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2020/10/4 9:37</i></td></tr>
<tr><td><b>作者：</b></td><td><i>YuGe</i></td></tr>
<tr><td><b>来源：</b></td><td><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test"><i>https://github.com/PhycheLiu/DesignPattern_Study-Test</i></a></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 12pt; font-weight: bold;">一、基本描述</span></div><ul><li><div><span style="font-size: 16px;">定义一系列的算法，把它们一个个封装起来，并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。</span></div></li><li><div><span style="font-size: 16px;">又称为政策（Policy）模式</span></div></li><li><div><span style="font-size: 12pt;">策略模式是一种定义一系列算法的方法，从概念上来看，所有这些算法完成的都是相同的工作，只是实现不同，它可以以相同的方式调用所有的算法，减少了各种算法类与使用算法类之间的耦合。</span></div></li><li><div><span style="font-size: 12pt;">策略模式用于算法的自由切换和扩展，它是应用较为广泛的设计模式之一。策略模式对应于解决某一问题的一个算法族，允许用户从该算法族中任选一个算法来解决某一问题，同时可以方便地更换算法或者增加新的算法。只要涉及到算法的封装、复用和切换都可以考虑使用策略模式。</span></div></li><li><div><span style="font-size: 16px;">策略模式是对算法的封装，它把算法的责任和算法本身分隔开，委派给不同的对象管理。</span><span style="font-size: 12pt;">策略模式将算法的定义与使用分开，也就是将算法的行为和环境分开，将算法的定义放在专门的策略类中（即具体策略类），每一个策略类封装了一种实现算法，使用算法的环境类针对抽 象策略类进行编程，符合“依赖倒转原则”。</span></div></li><li><div><span style="font-size: 16px;">策略模式的本质是使用面向对象的继承和多态，非常容易理解和掌握。</span></div></li><li><div><span style="font-size: 12pt;">在策略模式中，可以定义一些独立的类来封装不同的算法，每一个类封装一种具体的算法，每一个封装算法的类都可以称之为一种策略(Strategy)，为了保证这些策略在使用时具有一致性，一般会提供一个抽象的策略类来做规则的定义，而每种算法则对应于一个具体策略类。</span></div></li><li><div><span style="font-size: 12pt;">在策略模式中，对环境类和抽象策略类的理解非常重要，环境类是需要使用算法的类。在一个系统中可以存在多个环境类，它们可能需要重用一些相同的算法。</span></div></li><li><div><span style="font-size: 16px;">适用性：</span></div></li><ul><li><div><span style="font-size: 12pt;">各个类只有在算法或行为上稍有不同的场景。</span><span style="font-size: 12pt;">许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。</span></div></li><li><div><span style="font-size: 12pt;">算法需要自由切换的场景。需要使用一个算法的不同变体。例如，你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时，可以使用策略模式。又比如，</span><span style="font-size: 12pt;">算法始终在进化，特别是一些站在技术前沿的行业，连业务专家都无法给你保证这样的系统规则能够存在多长时间，在这种情况下应选用策略模式。</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">一个系统需要动态地在几种算法中选择一种，那么可以将这些算法封装到一个个的具体算法类中，而这些具体算法类都是一个抽象算法类的子类。换言之，这些具体算法类均有统一的接口，根据“里氏代换原则”和面向对象的多态性，客户端可以选择使用任何一个具体算法类，并只需要维持一个数据类型是抽象算法类的对象。</span><br/></div><ul><ul><li><div><span style="font-size: 12pt;">需要屏蔽算法那规则的场景。算法使用客户不应该知道的数据，或者</span><span style="font-size: 12pt;">不希望客户端知道复杂的、与算法相关的数据结构。</span><span style="font-size: 12pt;">很多算法知道一个名字即可，传递相关数字到算法当中，算法反馈一个结果。</span><span style="font-size: 12pt;">可使用策略模式</span><span style="color: unset; font-family: unset; font-size: 12pt;">在具体策略类中封装算法与相关的数</span><span style="color: unset; font-family: unset; font-size: 12pt;">据结构，可以提高算法的保密性与安全性。</span></div></li></ul><ul><li><div><span style="font-size: 12pt;">一个类定义了多种行为，并且这些行为在这个类的操作中以多个条件语句的形式出现。</span><span style="font-size: 12pt;">此时，使用策略模式，</span><span style="color: unset; font-family: unset; font-size: 12pt;">将相关的条件分支移人它们各自的 ConcreteStrategy 类中以代替这些条件语句</span><span style="font-size: 12pt;">，就可以避免使用难</span><span style="color: unset; font-family: unset; font-size: 12pt;">以维护的多重条件选择语句。</span></div></li></ul><li><div><span style="font-size: 16px;">对于实现的考虑</span></div></li><ul><li><div><span style="font-size: 12pt;"><b>定义 Strategy 和 Context 接口</b>。Strategy 和 Context 接口必须使得 ConcreteStrategy 能够有效的访问它所需要的 Context 中的任何数据，反之亦然。</span></div></li><ul><li><div><span style="font-size: 12pt;">一种办法是让 Context 将数据放在参数中传递给 Strategy 操作。也就是说，将数据发送给 Strategy。这使得 Strategy 和 Context 解耦。但另一方面， Context 可能发送一些 Strategy 不需要的数据。</span></div></li><li><div><span style="font-size: 12pt;">另一种办法是让 Context 将自身作为一个参数传递给 Strategy，该 Strategy 再显式地向该 Context 请求数据。或者， Strategy 可以存储对它的 Context 的一个引用，这样根本不再需要传递任何东西。</span></div></li></ul></ul></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">这两种情况下， Strategy都可以请求到它所需要的数据。但现在 Context必须对它的数据定义一个更为精细的接口，这将 Strategy和 Context更紧密地耦合在一起。</span></div><ul><ul><li><div><span style="font-size: 12pt;"><b>将 Strategy 作为模板参数</b>。在 C++ 中，可利用模板机制用一个 Strategy 来配置一个类。然而这种技术仅当下面条件满足时才可以使用：</span></div></li><ul><li><div><span style="font-size: 12pt;">可以在编译时选择 Strategy</span></div></li><li><div><span style="font-size: 12pt;">它不需在运行时改变</span></div></li></ul></ul></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">在这种情况下，要被配置的类（如， Context）被定义为以一个 Strategy 类作为一个参数的模板类。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">使用模板不再需要定义给 Strategy 定义接口的抽象类。把 Strategy 作为一个模板参数也使得可以将一个 Strategy 和它的 Context 静态地绑定在一起，从而提高效率。</span></div><ul><ul><li><div><span style="font-size: 12pt;"><b>使 Strategy对象成为可选的。</b>如果即使在不使用额外的 Strategy 对象的情况下， Context 也还有意义的话，那么它还可以被简化。 Context 在访问某 Strategy 前先检查它是否存在，如果有，那么就使用它；如果没有，那么 Context 执行缺省的行为。这种方法的好处是客户根本不需要处理 Strategy 对象，除非它们不喜欢缺省的行为。【<i>C++ STL中很多函数都有这种行为特征</i>】</span></div></li><li><div><span style="font-size: 16px;">Strategy 对象通常是很好的轻量级对象，适合与 Flyweight 模式混合使用。</span></div></li><li><div><span style="font-size: 16px;">可以将策略模式同简单工厂模式结合：在 Context 类中实现创建具体算法的简单工厂。</span></div></li><li><div><span style="font-size: 16px;">策略模式是一个非常简单的模式，在项目中使用非常多，但是它单独使用的地方比较少，因为它有致命缺陷：所有策略都需要暴露出去，这样客户端才方便决定使用哪一个策略。在实际的项目中，一般使用工厂方法模式来实现策略类的声明。</span></div></li></ul><li><div><span style="font-size: 16px;">策略模式封装了变化：对于</span><span style="font-size: 12pt; color: unset; font-family: unset;">面向对象的编程，并不是类越多越好，类的划分是为了封装，但分类的基础是抽象，具有相同属性和功能的对象的抽象集合才是类。算法本身只是一种策略，最重要的是这些算法是随时都可能相互替换的，这就是变化点，而封装变化点是面向对象编程的一种很重要的思维方式。</span></div></li><li><div><span style="font-size: 12pt;">策略模式就是用来封装算法的，但在实践中，可以用它来封装几乎任何类型的规则，只要在分析过程中听到需要在不同时间应用不同的业务规则，就可以考虑使用策略模式处理这种变化的可能性。</span></div></li><li><div><span style="font-size: 16px;">如果系统中的一个策略家族的具体策略数量超过4个，则需要考虑使用混合模式，解决策略类膨胀和对外暴露的问题。</span></div></li><li><div><span style="font-size: 16px;">实际应用</span></div></li><ul><li><div><span style="font-size: 12pt;">Java SE 的容器布局管理</span></div></li><li><div><span style="font-size: 16px;">微软的演示项目 PetShop 4.0</span></div></li></ul></ul><div><span style="font-size: 12pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="0.设计模式专题_files/Image [50].png" type="image/png" data-filename="Image.png"/><br/></div><ul><li><div><span style="font-size: 16px; font-weight: bold;">Strategy（策略）</span><span style="font-size: 16px;">：定义所有支持的算法的公共接口。Context 使用这个接口来调用某 ConcreteStrategy 定义的算法。是所有策略的父类，可以使抽象类或具体类，也可以是接口。是算法家族的抽象，定义每个算法或策略必须具有的方法和属性。</span></div></li><li><div><span style="font-size: 16px;"><b>ConcreteStrategy（具体策略）</b>：以 Strategy 接口实现某具体算法。实现抽象策略中的操作，含有具体的算法。在运行时，具体策略类将覆盖在环境类中定义的抽象策略类对象，使用一种具体的算法实现某个业务处理。</span></div></li><li><div><span style="font-size: 16px;"><b>Context（上下文）</b>：用一个 ConcreteStrategy 对象来配置。维护一个对 Strategy 对象的引用，用于定义所采用的策略。可定义一个接口来让 Strategy 访问它的数据。让客户与具体算法隔离，指定一个具体策略，保证策略可以互相替换。起到承上启下的封装作用，屏蔽高层模块对策略、算法的直接访问，封装可能存在的变化。Context 是策略模式的重点，借用了代理模式的思路，与代理模式的差别就是策略模式的封装角色和被封装的策略类不用是同一个接口。环境类是使用算法的角色，它在解决某个问题（即实现某个方法）时可以采用多种策略。</span></div></li><li><div><span style="font-size: 12pt; font-weight: bold;">协作：</span></div></li><ul><li><div><span style="font-size: 12pt;">Strategy 和 Context 相互作用以实现选定的算法。当算法被调用时， Context 可以将该算法所需要的所有数据都传递给该 Stategy。或者， Context 可以将自身作为一个参数传递给 Strategy 操作。这就让 Strategy在需要时可以回调 Context</span></div></li><li><div><span style="font-size: 12pt;">Context 将它的客户的请求转发给它的 Strategy。客户通常创建并传递一个 ConcreteStrategy 对象给该 Context；这样，客户仅与 Context 交互。通常有一系列的 ConcreteStrategy类可供客户从中选择。</span></div></li></ul></ul><div><span style="font-size: 12pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-size: 12pt; font-weight: bold;">优点</span></div><ul><li><div><span style="font-size: 12pt;"><b>定义了一个算法系列</b>。Strategy 类层次为 Context 定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。</span><span style="font-size: 12pt;">策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族，恰当使用继承可以把公共的代码移到抽象策略类中，从而避免重复的代码。</span></div></li><li><div><span style="font-size: 12pt;"><b>一个替代继承的方法</b>。继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个 Context 类的子类，从而给它以不同的行为。但这会将行为硬行编制到 Context 中，而将算法的实现与 Context 的实现混合起来，从而使 Context 难以理解、难以维护和难以扩展，也就是说，这种实现将算法的使用和算法本身混合在一起，从而不可能再独立演化</span><span style="font-size: 12pt;">，而且使用继承无法实现算法或行为在程序运行时的动态切换</span><span style="font-size: 12pt;">。最后你得到一堆相关的类，它们之间的唯一差别是它们所使用的算法或行为。将算法封装在独立的 Strategy类中使得你可以独立于其 Context改变它，使它易于切换、易于理解、易于扩展。【<i>实质上就是增加了一个 Context 作为间接类</i>】</span></div></li><li><div><span style="font-size: 12pt;"><b>消除了一些条件语句</b>。Strategy 模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时，很难避免使用条件语句来选择合适的行为。使用策略模式后，可以由其他模块决定采用何种策略，策略家族对外提供的访问接口就是封装类，将行为封装在一个个独立的 Strategy 类中消除了这些条件语句。</span></div></li><li><div><span style="font-size: 16px;"><b>可灵活选择具体实现</b>。</span><span style="font-size: 12pt;">Strategy 模式可以提供相同行为的不同实现。客户可以根据不同时间/空间权衡取舍要求从不同策略中进行选择。</span></div></li></ul><div style="margin-left: 40px;"><font style="font-size: 12pt;"><b>算法可以自由切换</b>。</font><span style="font-size: 12pt;">只要实现抽象策略，它就成为策略家族的一个成员，通过封</span><span style="font-size: 12pt; color: unset; font-family: unset;">装角色对其进行封装，保证对外提供“可自由切换”的策略。</span></div><ul><li><div><span style="font-size: 16px;"><b>简化了单元测试</b>。每个算法都有自己的类，可以通过自己的接口单独测试每个算法可保证自身没有错误，修改其中的任何一个算法时，也不会影响其他算法。</span></div></li><li><div><span style="font-size: 16px;"><b>扩展性良好</b>。</span><span style="font-size: 12pt;">提供了对“开闭原则”的完美支持，用户可以在不修改原有系统的基础上选择算法或行为，也可以灵活地增加新的算法或行为。</span></div></li><li><div><span style="font-size: 16px;">提供了一种算法的复用机制。</span><span style="font-size: 12pt;">由于将算法单独提取出来封装在策略类中，因此不</span><span style="font-size: 12pt; color: unset; font-family: unset;">同的环境类可以方便地复用这些策略类。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">缺点</span></div><ul><li><div><span style="font-size: 12pt;"><b>客户必须了解不同的 Strategy。</b>本模式有一个潜在的缺点，就是一个客户要选择一个合适的 Strategy就必须知道这些 Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时，才需要使用 Strategy模式。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt;">在基本的策略模式中，选择所用具体实现的职责由客户端对象承担，并转给策略模式的 Context 对象。客户端必须知道所有的策略类，并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别，以便适时选择恰当的算法，这本身并没有解除客户端需要选择判断的压力，而策略模式与简单工厂模式结合后，选择具体实现的职责也可以由 Context 来承担，这就最大化地减轻了客户端的职责。</span><br/></div><div style="margin-left: 40px;"><font style="font-size: 12pt;"><b>所有策略类均需要对外暴露</b>。</font><span style="font-size: 12pt;">上层模块必须知道有哪些策略，然后才能决定使用哪一个策略，这与迪米特法则是相违</span><span style="font-size: 12pt; color: unset; font-family: unset;">背的：想要使用某一个具体的策略类，必须先要了解这个策略。</span></div><ul><li><div><span style="font-size: 12pt;"><b>Strategy 和 Context 之间的通信开销。</b>无论各个 ConcreteStrategy 实现的算法是简单还是复杂，它们都共享 Strategy 定义的接口。因此很可能某些 ConcreteStrategy 不会都用到所有通过这个接口传递给它们的信息；简单的 ConcreteStrategy 可能不使用其中的任何信息！这就意味着有时 Context 会创建和初始化一些永远不会用到的参数。</span></div></li><li><div><span style="font-size: 12pt;"><b>增加了对象的数目。</b>每一个策略都是一个类，任何细小的变化都将导致系统要增加一个新的具体策略类，这些具体策略类复用的可能性很小，类的数量增多。Strategy 增加了一个应用中的对象的数目。可以结合享元模式（Flyweight）缓解这一问题。</span></div></li><li><div><span style="font-size: 12pt;"><b>无法同时在客户端使用多个策略类。</b>也就是说，在使用策略模式时，客户端每次只能使用一个策略类，不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">四、可能的改进</span></div><ul><li><div><span style="font-size: 16px;">可以使用策略枚举改进策略模式。【<i>但是个人对策略枚举持保留意见，只不过思路比较独特，但是实际上《设计模式之禅》这本书中对于策略枚举的例子是由问题的，所以个人认为可以简单的了解一下思路即可</i>】</span></div></li><li><div><span style="font-size: 16px;">可以与 Flyweight、工厂方法、简单工厂等模式混合使用。</span></div></li><li><div><span style="color: rgb(36, 41, 46); font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;; font-size: 16px; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;"><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/strategy/sample01">可以利用“xml + 反射”机制改进策略模式。在 xml 当中指定使用哪个具体策略。</a></span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">五、个人理解</span></div><ul><li><div><span style="font-size: 16px;">策略模式实现与思路非常简单。而且没有太多的变种，改进的总体思路是与其他设计模式混合使用。</span></div></li><li><div><span style="font-size: 16px;">在以前的实践当中，很多编码可以选择策略模式，且很多已经使用了策略模式。</span></div></li><li><div><span style="font-size: 16px;">每次遇到多条件语句时，均可想想是否可以选用设计模式替换：</span></div></li><ul><li><div><span style="font-size: 16px;">与状态相关的可以考虑选用状态模式</span></div></li><li><div><span style="font-size: 16px;">与状态无关的可以考虑选用策略模式</span></div></li></ul><li><div><span style="font-size: 16px;">单独使用策略模式固然好，但是最希望的还是结合策略模式实现动态的算法添加，如在C++中使用动态加载的动态链接库，Java 中使用“xml+反射机制”实现动态选择算法。</span></div></li><li><div><span style="font-size: 16px;">策略是特殊的代理（应该是指 Context 类），是面向对象中继承的应用（应该是指 Strategy 体系），其中核心是 Context，在算法与客户之间增加了一重间接性，使得实现更灵活。</span></div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br/></div><div><br/></div></div></span>
</div>
<hr>
<a name="26642"/>
<h1>22.模板方法（Template Method）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/10/11 5:33</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/16 7:48</i></td></tr>
<tr><td><b>作者：</b></td><td><i>YuGe</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 12pt; font-weight: bold;">一、基本描述</span></div><ul><li><div><span style="font-size: 12pt;">定义一个操作中的算法的骨架，而将一些步骤延迟到子类中。 TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。</span></div></li><li><div><span style="font-size: 16px;">模板方法定义了一个算法的步骤，并允许子类为一个或多个步骤提供实现。</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">模板方法与具体方法</span></span></div></li><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">模板方法</span><span style="font-size: 16px; font-weight: bold;">：</span>一个模板方法用一些抽象的操作定义一个算法，而子类将冲定义这些操作以提供具体的行为。通过使用抽象操作定义一个算法中的一些步骤，模板方法确定了它们的先后顺序，但它允许子类改变这些具体步骤以满足它们各自的需求。【模板方法的精髓是模板方法本身是不可变的，但是其中调用的操作可以由子类改变，即</span><span style="font-size: 16px; color: unset; font-family: unset;">每一个具体步骤肯定是可变的，但步骤本身不可变】。</span></div><div><span style="font-size: 12pt;">一个模板方法是定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或一个总行为的方法。这个模板方法定义在抽象类中，并由子类不加以修改地完全继承下来。</span></div><div><span style="font-size: 12pt;">模板方法是一个具体方法，它给出了一个顶层逻辑框架，而逻辑的组成步骤在抽象类中可以是具体方法，也可以是抽象方法。由于模板方法是具体方法，因此模板方法模式中的抽象层只能是抽象类，而不是接口。</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">基本方法：</span>基本方法是实现算法各个步骤的方法，是模板方法的组成部分。基本方法可分为三种：</span></div></li><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">抽象方法</span>：</span><span style="font-size: 12pt;">一个抽象方法由抽象类声明、由其具体子类实现。【也就是所谓的原语操作，主要体现动态绑定，体现不同的子类不同，是子类必须重新实现的方法】</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">具体方法</span>：</span><span style="font-size: 12pt;">一个具体方法由一个抽象类或具体类声明并实现，其子类可以进行覆盖也可以直接继承。【主要体现为子类中的公共操作，这些操作抽取到基类当中，子类可以修改，但一般不应该修改】</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">钩子方法</span>：</span><span style="font-size: 12pt;">一个钩子方法由一个抽象类或具体类声明并实现，而其子类可能会加以扩展。通常在父类中给出的实现是一个空实现，并以该空实现作为方法的默认实现，当然钩子方法也可以提供一个非空的默认实现。【基类中有实现，子类中可能会修改的特殊的具体方法】</span></div></li><ul><li><div><span style="font-size: 16px; color: unset; font-family: unset;">钩子是一种被声明在抽象类中的方法，但只有空的或者默认的实现。钩子的存在，可以让子类有能力对算法的不同点进行挂钩。要不要挂钩，由子类自行决定。</span></div></li><li><div><span style="font-size: 12pt;">它提供了缺省的行为，子类可以在必要时进行扩展。</span></div></li><li><div><span style="font-size: 16px;">钩子方法的引入使得子类可以控制父类的行为。</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">很重要的一点是模板方法应该指明哪些操作是钩子操作（可以被重定义）以及哪些是抽象操作（必须被重定义）。要有效地复用一个抽象类，子类编写者必须明确了解哪些操作是设计为有待重定义的。</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">子类可以通过重定义父类的操作来扩展该操作的行为，其间可显式地调用父类操作【一般思路】。</span><span style="font-size: 12pt; color: unset; font-family: unset;">不幸的是，人们很容易忘记去调用被继承的行为。我们可以将这样一个操作转换为一个模板方法，以使得父类可以对子类的扩展方式进行控制。也就是，在父类的模板方法中调用钩子操作，子类可以重定义这个钩子操作。</span></div></li><li><div><span style="font-size: 16px;">钩子的用法：</span></div></li><ul><li><div><span style="font-size: 16px;">钩子可以让子类实现算法中可选的部分。</span><span style="font-size: 16px; color: unset; font-family: unset;">在钩子对于子类实现不重要的时候，子类可以对此钩子置之不理。</span></div></li><li><div><span style="font-size: 16px;">让子类能够有机会对模板方法中某些即将发生的（或者刚刚发生的）步骤做出反应。</span></div></li><li><div><span style="font-size: 16px;">钩子可以让子类有能力为其抽象类作一些决定。</span></div></li></ul><li><div><span style="font-size: 16px; color: unset; font-family: unset;">钩子方法有两类：</span></div></li></ul><ul><ul><li><div><span style="font-size: 12pt;">一类钩子方法可以与一些具体步骤“挂钩”，以实现在不同条件下执行模板方法中的不同步骤，这类钩子方法的返回类型通常是bool类型的，这类方法名一般为IsXXX()，用于对某个条件进行判断，如果条件满足则执行某一步骤，否则将不执行。</span></div></li><li><div><span style="font-size: 16px;">另一类</span><span style="font-size: 12pt;">钩子方法就是实现体为空的具体方法，子类可以根据需要覆盖或者继承这些钩子方法，与抽象方法相比，这类钩子方法的好处在于子类如果没有覆盖父类中定义的钩子方法，编译可以正常通过，但是如果没有覆盖父类中声明的抽象方法，编译将报错。</span></div></li></ul></ul><li><div><span style="font-size: 16px;">当子类“必须”提供算法中某个方法或步骤的实现时，就使用抽象方法，如果算法的这个部分是可选的，就用钩子，如果是钩子的话，子类可以选择实现这个钩子，但并不强制这么做。</span></div></li></ul></ul><li><div><span style="font-size: 12pt;">模板方法是一种代码复用的基本技术。它们在类库中尤为重要，它们提取了类库中的公共行为。</span></div></li><li><div><span style="font-size: 16px;">模板方法模式用来创建一个算法的模板。</span></div><div><span style="font-size: 16px;">模板：模板是一个方法。更具体的说，这个方法将算法定义成一组步骤，其中的任何步骤都可以是抽象的，由子类负责实现。这可以确保算法的结构保持不变，同时由子类提供部分实现。</span></div></li><li><div><span style="font-size: 12pt;">模板方法导致一种反向的控制结构，这种结构有时被称为“好莱坞法则”，即“别找我们，我们找你”。这指的是一个父类调用一个子类的操作，而不是相反。</span></div></li><li><div><span style="font-size: 12pt;">既然用了继承，并且肯定这个继承有意义，就应该要成为子类的模板，所有重复的代码都应该要上升到父类去，而不是让每个子类都去重复。模板方法模式就是通过把不变的行为搬移到超类，去除子类中的重复代码来体现它的优势。在软件开发过程中，如果相同的一段代码复制过两次，就需要对设计产生怀疑。</span></div></li><li><div><span style="font-size: 12pt;">当我们要完成在某一细节层次一致的一个过程或一系列步骤，但其个别步骤在更详细的层次上的实现可能不同时，我们通常考虑用模板方法模式来处理。父类建立框架，子类在重写了父类部分的方法后，再调用从父类继承的方法，产生了不同的结果，从而使得相同的算法框架可以有不同的执行结果。</span></div></li><li><div><span style="font-size: 16px;">模板方法模式提供了一个很好的代码复用平台。</span></div></li><li><div><span style="font-size: 12pt;">某个方法的实现需要多个步骤，其中有些步骤是固定的，而有些步骤并不固定，存在可变性。为了提高代码的复用性和系统的灵活性，可以选择使用模板方法模式。</span><span style="font-size: 12pt; color: unset; font-family: unset;">当不变的和可变的行为在方法的子类实现中混合在一起的时候，不变的行为就会在子类中重复岀现。可以通过模板方法模式把这些行为搬移到单一的地方，这样就帮助子类摆脱重复的不变行为的纠缠。</span></div></li><li><div><font style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">适用性</span></font></div></li><ul><li><div><span style="font-size: 12pt;">有时候会遇到由一系列步骤构成的算法需要执行，</span><span style="font-size: 12pt;">对</span><span style="font-size: 12pt;">重要、</span><span style="font-size: 12pt;">复杂的算法进行分割，</span><span style="font-size: 12pt;">这个算法从高层次上看是相同的，但有些步骤的实现可能不同。</span><span style="font-size: 12pt;">可以把核心算法设计为模板方法，</span><span style="font-size: 12pt;">将其算法中固定不变的部分设计为模板方法和父类具体方法，而</span><span style="font-size: 12pt;">一些可以改变的细节及</span><span style="font-size: 12pt;">周边的相关细节功能</span><span style="font-size: 12pt;">由其子类来实现。</span><span style="font-size: 12pt;">即：一次性实现一个算法的不变部分，并将可变的行为留给子类来实现。</span></div></li><li><div><span style="font-size: 12pt;">各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。</span><span style="font-size: 12pt;">多个子类有公有的方法，并且逻辑基本相同时。</span><span style="font-size: 12pt;">各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。这是“重分解以一般化”的一个很好的例子。首先识别现有代码中的不同之处，并且将不同之处分离为新的操作。最后，用一个调用这些新的操作的模板方法来替换这些不同的代码。</span></div></li><li><div><span style="font-size: 12pt;">控制子类扩展。模板方法只在特定点调用“hook”操作，这样就只允许在这些点进行扩展。</span></div></li><li><div><span style="font-size: 12pt;">需要通过子类来决定父类算法中某个步骤是否执行，实现子类对父类的反向控制。</span><span style="font-size: 12pt;">重构时，模板方法模式是一个经常使用的模式，把相同的代码抽取到父类中，然后通过钩子函数约束其行为。</span></div></li></ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">实现上的考虑</span></span></div></li><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">使用访问控制</span></span></div><div><span style="font-size: 12pt;">一个模板方法调用的原语操作可以被定义为 protected 成员。</span><span style="font-size: 12pt; color: unset; font-family: unset;">这保证它们只被模板方法调用。必须重定义的原语操作须定义为纯虚函数。模板方法自身不需被重定义，因此可以将模板方法定义为一个非虚成员函数。</span></div></li><li><div><font style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">尽量减少原语操作</span></font></div><div><span style="font-size: 12pt;">定义模板方法的一个重要目的是尽量减少一个子类具体实现该算法时必须重定义的那些原语操作的数目。需要重定义的操作越多，客户程序就越冗长。</span></div></li><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">命名约定</span></span></div><div><span style="font-size: 12pt;">可以给应被重定义的那些操作的名字加上一个前级以识别它们。</span></div></li><li><div><span style="font-size: 16px;">为防止子类改变模板方法中的算法，可以将模板方法声明为final。</span></div></li></ul><li><div><span style="font-size: 16px;">模板方法模式的实际应用：</span></div></li><ul><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">模板方法模式是基于继承的代码复用技术，</span><span style="font-size: 16px; color: unset; font-family: unset;">仅使用了继承机制，</span><span style="font-size: 12pt; color: unset; font-family: unset;">它体现了面向对象的诸多重要思想，是一种使用较为频繁的模式。</span><span style="font-size: 16px; color: unset; font-family: unset;">几乎可以在任何一个抽象类中找到。对继承和多态有一定认识的人几乎都会在继承体系中使用到模板方法模式。</span></div></li><li><div><span style="font-size: 12pt;">模板方法模式广泛应用于框架设计中，以确保通过父类来控制处理流程的逻辑顺序（如框架的初始化，测试流程的设置等）。</span></div><div><span style="font-size: 12pt;">由框架控制如何做事情，由用户指定算法中每个步骤的实现。</span></div></li><li><div><span style="font-size: 16px;">真实世界的代码中有很多模板方法模式的变体。</span></div></li></ul><li><div><span style="font-size: 16px;">与其他模式的关系</span></div></li><ul><li><div><span style="font-size: 16px;">策略模式和模板方法模式都封装算法，一个用组合，一个用继承</span></div></li><li><div><span style="font-size: 16px;">工厂方法是模板方法的一个特殊版本。</span></div></li></ul></ul><div><span style="font-size: 12pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="0.设计模式专题_files/Image [51].png" type="image/png" data-filename="Image.png"/></div><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">AbstractClass（抽象类）：</span></span></div></li><ul><li><div><span style="font-size: 16px;">模板方法模式的核心是抽象类和其中模板方法的设计。</span></div></li><li><div><span style="font-size: 12pt;">定义抽象的原语操作（ primitive operation），具体的子类将重定义它们以实现一个算法的各步骤。模板方法本身和这两个操作的具体实现之间被解耦了。</span></div></li><li><div><span style="font-size: 12pt;">实现一个模板方法（template method），定义一个算法的骨架，完成固定的逻辑。该模板方法不仅调用原语操作，也调用定义在  Abstractclass 或其他对象中的操作。提供一些抽象的行为，放到子类中实现。</span><span style="font-size: 12pt;">这个模板方法一般是一个具体方法，给出了一个顶级逻辑的框架，而逻辑的组成步骤在相应的抽象操作中，推迟到子类实现（指元语操作），顶级逻辑也有可能调用一些具体方法。</span></div></li><li><div><span style="font-size: 16px;">为防止恶意操作，Java 中，模板方法都加上 final 关键字，不能覆写，而原语操作尽量设计为 protected 类型。</span></div></li></ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">ConcreteClass（具体类）：</span></span></div></li><ul><li><div><span style="font-size: 16px;">实现原语操作以完成算法中不变的步骤。实现具体的逻辑步骤。</span></div></li><li><div><span style="font-size: 16px;">具体类中也可以覆写父类中已经实现的具体操作。</span></div></li><li><div><span style="font-size: 12pt;">实现父类定义的一个或多个抽象方法，每一个 AbstractClass 都可以有任意多个 ConcreteClass 与之对应，而每一个 ConcreteClass 都可以给出这些抽象方法的不同实现，从而使得顶级逻辑的实现各不相同，其实也是一个抽象模板。</span></div></li></ul><li><div><span style="font-size: 12pt; font-weight: bold;">协作：</span></div></li><ul><li><div><span style="font-size: 12pt;">ConcreteClass 靠 AbstractClass 来实现算法中不变的步骤。</span></div></li><li><div><span style="font-size: 16px;">AbstractClass 靠 ConcreteClass 来实现具体的一些操作。</span></div></li></ul></ul><div><span style="font-size: 12pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-size: 12pt; font-weight: bold;">优点</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">封装不变部分，扩展可变部分。</span></span><span style="font-size: 12pt;">模板方法模式是一种代码复用技术，它在类库设计中尤为重要，它提取了类库中的公共行为，将公共行为放在父类中，而通过其子类来实现不同的行为，</span><span style="font-size: 12pt;">通过继承来继续扩展。</span><span style="font-size: 12pt;">它鼓励我们恰当使用继承来实现代码复用。</span></div></li><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">提取公共部分代码，便于维护。</span></span></div></li><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">行为由父类控制，子类实现。</span></span></div></li><ul><li><div><span style="font-size: 12pt;">在父类中形式化地定义一个算法，而由它的子类来实现细节的处理，在子类实现详细的处理算法时并不会改变算法中步骤的执行次序。</span></div></li><li><div><span style="font-size: 12pt;">基本方法是由子类实现的，因此子类可以通过扩展的方式增加相应的功能，符合开闭原则。</span></div></li></ul><li><div><span style="font-size: 12pt;">通过使用模板方法模式，一方面提高了代码的</span><span style="font-size: 12pt; font-weight: bold;">复用性</span><span style="font-size: 12pt;">，另一方面还可以利用面向对象的多态性，在运行时选择一种具体子类，提高系统的</span><span style="font-size: 12pt; font-weight: bold;">灵活性</span><span style="font-size: 12pt;">和</span><span style="font-size: 12pt; font-weight: bold;">可扩展性</span><span style="font-size: 12pt;">。</span></div></li><li><div><span style="font-size: 12pt;">可实现一种</span><span style="font-size: 12pt; font-weight: bold;">反向控制结构</span><span style="font-size: 12pt;">，通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行。</span></div></li><li><div><span style="font-size: 12pt;">在模板方法模式中可以通过子类来覆盖父类的基本方法，不同的子类可以提供基本方法的不同实现，更换和增加新的子类很方便，符合</span><span style="font-size: 12pt; font-weight: bold;">单一职责原则</span><span style="font-size: 12pt;">和</span><span style="font-size: 12pt; font-weight: bold;">开闭原则</span><span style="font-size: 12pt;">。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">缺点</span></div><ul><li><div><span style="font-size: 12pt;">按一般的设计习惯，抽象类负责声明最抽象、最一般的事物属性和方法，实现类完成具体的事物属性和方法。但是模板方法模式却颠倒了，抽象类定义了部分抽象方法，由子类实现，子类执行的结果影响了父类的结果，也就是子类对父类产生了影响，这在复杂的项目中，会带来代码阅读的难度。</span></div></li><li><div><span style="font-size: 12pt;">需要为每一个基本方法的不同实现提供一个子类，如果父类中可变的基本方法太多，将会导致类的个数增加，系统更加庞大，设计也更加抽象，此时，可结合桥接模式来进行设计。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">四、可能的改进</span></div><ul><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/strategy/sample01" style="font-size: 16px; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;; font-variant-caps: normal; font-variant-ligatures: normal;">可以利用“xml + 反射”机制改进策略模式。在 xml 当中指定使用哪个具体策略。</a></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">五、个人理解</span></div><ul><li><div><span style="font-size: 16px;">模板方法模式非常简单，应用非常广泛，日常编程的时候经常会用到，尤其是其中提取公共代码的部分。</span></div></li><li><div><span style="font-size: 16px;">实际上感觉模板方法模式最精髓的部分应该是其中的模板方法。这个模板方法在很多框架中可以看到，并且大量应用过，这个模式非常实用，可以尝试实践中应用一下。</span></div></li><li><div><span style="font-size: 16px;">钩子方法其实也没有那么神秘，这个模式当中的论述还是非常清晰的。</span></div></li><li><div><span style="font-size: 16px;">基类中定义的基本方法有三类，个人的理解：</span></div></li><ul><li><div><span style="font-size: 16px;">抽象方法：这里的抽象方法实际上就是《</span><span style="font-size: 12pt;">设计模式：可复用面向对象软件的基础</span><span style="font-size: 16px;">》书中提到的“原语操作”，主要体现动态绑定，体现不同的子类不同实现，子类中必须要重新实现这类方法。</span></div></li><li><div><span style="font-size: 16px;">具体方法：主要体现将子类中公共操作提取到基类当中，基类中实现这个方法，子类中可以覆盖这个方法并且重新实现，但是一般不会修改基类中的实现。</span></div></li><li><div><span style="font-size: 16px;">钩子方法：一个特殊的具体方法，基类中实现这个方法，但是子类中可能会修改基类当中的实现，体现子类对基类的反向控制，也体现了不同的子类实现不同。</span></div><div><span style="font-size: 16px;">钩子方法与“回调”的概念非常相似，只不过实现机制和实现目的有一些不同。一般的回调函数使用函数指针或者函数对象实现，如果不提供具体的实现，则编译报错。而钩子方法强调的是子类当中可以实现，也可以不实现，不实现使用基类中的默认实现，此时编译正常。</span></div></li></ul></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br/></div><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="26971"/>
<h1>0.面向对象设计原则</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/10/24 12:58</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/27 6:07</i></td></tr>
<tr><td><b>作者：</b></td><td><i>YuGe</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 12pt; font-weight: bold;">〇、概述</span></div><ul><li><div><span style="font-size: 16px;">主要针对设计软件的可维护性和可复用性</span></div></li><li><div><span style="font-size: 16px;">所有的设计模式都有一个统一的思路：封装变化的部分！</span></div><div><span style="font-size: 12pt;">分析变化的部分是什么，如何封装。</span></div></li><li><div><span style="font-size: 12pt;">软件设计最大的难题就是应对需求的变化，但是纷繁复杂的需求变化又是不可预料的。我们要为不可预料的事情做好准备，这本身就是一件非常痛苦的事情，但是大师们还是给我们提出了非常好的6大设计原则以及23个设计模式来“封装”未来的变化。</span></div></li><li><div><span style="font-size: 16px;">最常用的是6种面向对象设计原则：</span></div></li><li><div><span style="font-size: 16px;">单一职责原则（Single Responsibility Principle, SRP）：一个类只负责一个功能领域中的相应职责。</span></div></li><li><div><span style="font-size: 16px;">开闭原则（Open-Closed</span> <span style="font-size: 16px;">Principle, OCP</span><span style="font-size: 16px;">）：</span><span style="font-size: 12pt;">软件实体应对扩展开放， 而对修改关闭。</span></div></li><li><div><span style="font-size: 16px;">里氏代换原则（Liskov Substitution</span> <span style="font-size: 16px;">Principle, LSP</span><span style="font-size: 16px;">）：</span><span style="font-size: 12pt;">所有引用基类对象的地方能够透明地使用</span><span style="font-size: 12pt; color: unset; font-family: unset;">其子类的对象</span><span style="font-size: 12pt; color: unset; font-family: unset;">。</span></div></li><li><div><span style="font-size: 16px;">接口隔离原则（Interface Segregation</span> <span style="font-size: 16px;">Principle, ISP</span><span style="font-size: 16px;">）：</span><span style="font-size: 12pt;">使用多个专门的接口，而不使用单一的总</span><span style="font-size: 12pt; color: unset; font-family: unset;">接口。</span></div></li><li><div><span style="font-size: 16px;">依赖倒转原则（Dependence Inversion</span> <span style="font-size: 16px;">Principle, DIP</span><span style="font-size: 16px;">）：抽象不应该依赖于细节，细节应该依赖于抽象。</span></div></li><li><div><span style="font-size: 16px;">迪米特法则（Law of Demeter, LoD）：一个软件实体应当尽可能少的与其他实体发生相互作用。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt;">把前6个原则的首字母（里氏替换原则和迪米特法则的首字母重复，只取一个）联合起来就是SOLID（solid，稳定的），其代表的含义也就是把这6个原则结合使用的好处：建立稳定、灵活、健壮的设计。</span></div><ul><li><div><span style="font-size: 16px;">面向对象设计的四个好处：可维护、可扩展、可复用、灵活性好。</span></div></li><li><div><span style="font-size: 12pt;">需要注意，设计是有限度的，不能无限地考虑未来的变更情况，否则就会陷入设计的泥潭中而不能自拔。</span></div><div><span style="font-size: 16px;">没有任何原则是法律，所有的原则都应该在有帮助的时候才遵守，所有的设计都不免需要折衷。虽然原则提供了方针，但在采用原则之前，必须全盘考虑所有的因素。</span></div></li><li><div><span style="font-size: 16px;">此外，还有</span><span style="font-size: 16px; font-weight: bold;">合成复用原则</span><span style="font-size: 16px;">、</span><span style="font-size: 16px; font-weight: bold;">松耦合原则</span><span style="font-size: 16px;">等。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">一、单一职责原则</span></div><ul><li><div><span style="font-size: 16px;">定义</span></div></li><ul><li><div><span style="font-size: 16px;">就一个类而言，应该仅有一个引起它变化的原因。</span></div></li><li><div><span style="font-size: 16px;">一个类只负责一个功能领域中的相应职责。</span></div></li></ul><li><div><span style="font-size: 12pt;">单一职责原则是实现高内聚、低耦合的指导方针，它是最简单但又最难运用的原则</span><span style="font-size: 16px;">，用于控制类的粒度大小。</span></div></li><li><div><span style="font-size: 12pt;">在编程时，很自然会给一个类增加各种各样的功能，这其实是很糟糕的，维护麻烦，复用不可能，也缺乏灵活性。</span></div></li><li><div><span style="font-size: 16px;">如果一个类承担的职责过多，就等于把这些职责耦合在一起，一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计。当变化发生时，设计会遭受到意想不到的破坏。</span></div></li><li><div><span style="font-size: 16px;">软件设计真正要做的许多内容，就是发现职责并把那些职责相互分离。</span></div></li><li><div><span style="font-size: 16px;">判断职责是否应当分离：如果你能够想到多于一个的动机去改变一个类，那么这个类就具有多于一个类的职责。</span></div></li><li><div><span style="font-size: 16px;">编程时，要在类的职责分离上多思考，做到单一职责，这样代码才是真正的易维护、易扩展、易复用。</span></div></li><li><div><span style="font-size: 12pt;">单一职责原则有什么好处：</span></div></li><ul><li><div><span style="font-size: 12pt;">类的复杂性降低，实现什么职责都有清晰明确的定义；</span></div></li><li><div><span style="font-size: 12pt;">可读性提高；</span></div></li><li><div><span style="font-size: 12pt;">可维护性提高；</span></div></li></ul><li><div><span style="font-size: 12pt;">变更引起的风险降低。变更是必不可少的，如果接口的单一职责做得好，一个接口修改只对相应的实现类有影响，对其他的接口无影响，这对系统的扩展性、维护性都有非常大的帮助。</span></div></li><li><div><span style="font-size: 12pt;">单一职责原则很难在项目中得到体现：</span></div></li><ul><li><div><span style="font-size: 12pt;">单一职责原则最难划分的就是职责。一个职责一个接口，但问题是“职责”没有一个量化的标准。</span></div></li><li><div><span style="font-size: 12pt;">单一职责原则提出了一个编写程序的标准，用“职责”或“变化原因”来衡量接口或类设计得是否优良，但是“职责”和“变化原因”都是不可度量的，因项目而异，因环境而异。</span></div></li><li><div><span style="font-size: 12pt;">对于接口，我们在设计的时候一定要做到单一，但是对于实现类就需要多方面考虑了。生搬硬套单一职责原则会引起类的剧增，给维护带来非常多的麻烦，而且过分细分类的职责也会人为地增加系统的复杂性。</span></div></li></ul><li><div><span style="font-size: 12pt;">单一职责适用于接口、类，同时也适用于方法，一个方法尽可能做一件事情，方法职责要清晰、单一，不要让别人猜测这个方法可能是用来处理什么逻辑的。</span></div></li><li><div><span style="font-size: 12pt;">类的单一职责确实受非常多因素的制约，纯理论地来讲，这个原则是非常优秀的，但是现实有现实的难处，你必须去考虑项目工期、成本、人员技术水平、硬件情况、网络情况甚至有时候还要考虑政府政策、垄断协议等因素。对于单一职责原则，接口一定要做到单一职责，类的设计尽量做到只有一个原因引起变化。</span></div></li><li><div><span style="font-size: 12pt;">单一职责原则告诉我们：一个类不能太“累”！在软件系统中，一个类（大到模块，小到方法）承担的职责越多，它被复用的可能性就越小，而且一个类承担的职责过多，就相当于将这些职责耦合在一起，当其中一个职责变化时，可能会影响其他职责的运作，因此要将这些职责进行分离，将不同的职责封装在不同的类中，即将不同的变化原因封装在不同的类中，如果多个职责总是同时发生改变则可将它们封装在同一类中。</span></div></li><li><div><span style="font-size: 16px;">我们知道要避免类内的改变，因为修改代码很容易造成许多潜在的错误。如果有一个类具有两个改变的原因，那么这会使得将来该类的变化几率上升，而当它真的改变时，设计中同时有两个方面将会受到影响。</span></div></li><li><div><span style="font-size: 16px;">单一 职责原则听起来很容易，但做起来并不简单：区分设计中的责任，是最困难的事情之一。</span></div><div><span style="font-size: 16px;">我们的大脑很习惯看着一大群的行为，然后将它们集中在一起，尽管它们可能属于两个或者多个不同的责任。想要成功的唯一方法，就是努力不懈的检查你的设计，随着系统成长，随时观察有没有迹象显示某个类改变的原因超出一个。</span></div></li><li><div><span style="font-size: 16px;">内聚（cohension）用来度量一个类或模块紧密的达到单一目的或责任。</span></div><div><span style="font-size: 16px;">当一个模块或一个类被设计成只支持一组相关的功能时，就说它具有高内聚，反之，当被设计成支持一组不相关的功能时，就说它具有低内聚。</span></div><div><span style="font-size: 16px;">内聚是一个比单一职责原则更普遍的概念，但两者其实关系是很紧密的。遵守这个原则的类容易具有很高的凝聚力，而且比背负许多责任的低内聚类更容易维护。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">二、开闭原则</span></div><ul><li><div><span style="font-size: 12pt;">定义</span></div></li><li><div><span style="font-size: 16px;">软件实体（类，模块，函数等等）应该可以扩展，但是不可以修改。</span></div></li><li><div><span style="font-size: 12pt;">一个软件实体如类、模块和函数应该对扩展开放，对修改关闭。</span></div></li><li><div><span style="font-size: 12pt;">软件实体应尽量在不修改原有代码的情况下进行扩展。</span></div></li><li><div><span style="font-size: 12pt;">开闭原则是Java世界里最基础的设计原则，它指导我们如何建立一个稳定的、灵活的系统。</span></div></li><li><div><span style="font-size: 12pt;">开闭原则是面向对象的可复用设计的第一块基石，它是最重要的面向对象设计原则。</span></div></li><li><div><span style="font-size: 12pt;">其含义是说一个软件实体应该通过扩展来实现变化，而不是通过修改已有的代码来实现变化。</span></div></li><li><div><span style="font-size: 12pt;">任何软件都需要面临一个很重要的问题，即它们的需求会随时间的推移而发生变化。当软件系统需要面对新的需求时，我们应该尽量保证系统的设计框架是稳定的。如果一个软件设计符合开闭原则，那么可以非常方便地对系统进行扩展，而且在扩展时无须修改现有代码，使得软件系统在拥有适应性和灵活性的同时具备较好的稳定性和延续性。随着软件规模越来越大，软件寿命越来越长，软件维护成本越来越高，设计满足开闭原则的软件系统也变得越来越重要。</span></div></li><li><div><span style="font-size: 12pt;">为了满足开闭原则，需要对系统进行抽象化设计，抽象化是开闭原则的关键。可以为系统定义一个相对稳定的抽象层，而将不同的实现行为移至具体的实现层中完成。如果需要修改系统的行为，无须对抽象层进行任何改动，只需要增加新的具体类来实现新的业务功能即可，实现在不修改已有代码的基础上扩展系统的功能，达到开闭原则的要求。</span></div></li><li><div><span style="font-size: 16px;">软件实体的定义</span></div></li><li><div><span style="font-size: 12pt;">项目或软件产品中按照一定的逻辑规则划分的模块。</span></div></li><li><div><span style="font-size: 16px;">抽象和类</span></div></li><li><div><span style="font-size: 16px;">方法</span></div></li><li><div><span style="font-size: 12pt;">一个软件产品只要在生命期内，都会发生变化，既然变化是一个既定的事实，我们就应该在设计时尽量适应这些变化，以提高项目的稳定性和灵活性，真正实现“拥抱变化”。开闭原则告诉我们应尽量通过扩展软件实体的行为来实现变化，而不是通过修改已有的代码来完成变化，它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。</span></div></li><li><div><span style="font-size: 12pt;">在业务规则改变的情况下高层模块必须有部分改变以适应新业务，改变要尽量地少，防止变化风险的扩散。</span></div></li><li><div><span style="font-size: 12pt;">开闭原则对扩展开放，对修改关闭，并不意味着不做任何修改，低层模块的变更，必然要有高层模块进行耦合，否则就是一个孤立无意义的代码片段。</span></div></li><li><div><span style="font-size: 16px;">虽然似乎有点矛盾，但是的确有一些技术可以允许在不直接修改代码的情况下对其进行扩展。</span></div></li><li><div><span style="font-size: 16px;">在选择需要被扩展的代码部分时要小心。每个地方都采用开放-关闭原则，是一种浪费，也没有必要，还会导致代码变得复杂且难以理解。</span></div></li><li><div><span style="font-size: 16px;">变化归纳为以下三种类型：</span></div></li><li><div><span style="font-size: 16px;">逻辑变化</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">只变化一个逻辑，而不涉及其他模块，</span><span style="font-size: 12pt;">可以通过修改原有类中的方法的方式来完成，前提条件是所有依赖或关联类都按照相同的逻辑处理。</span></div><ul><li><div><span style="font-size: 16px;">子模块变化</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">一个模块变化，会对其他的模块产生影响，特别是一个低层次的模块变化必然引起高层模块的变化，因此在通过扩展完成变化时，高层次的模块修改是必然的。</span></div><ul><li><div><span style="font-size: 16px;">可见视图变化</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">可见视图是提供给客户使用的界面，</span><span style="font-size: 12pt;">该部分的变化一般会引</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">起连锁反应，</span> <span style="font-size: 12pt;">最司空见惯的是业务耦合变化，</span><span style="font-size: 12pt;">但还是可以通过扩展来完成变化，这就要看我们原有的设计是否灵活。</span></div><ul><li><div><span style="font-size: 12pt;">应对变化逻辑：</span></div></li><li><div><span style="font-size: 12pt;">首先是我们有一个还算灵活的设计；</span></div></li><li><div><span style="font-size: 12pt;">然后有一个需求变化，我们通过扩展一个子类拥抱了变化；</span></div></li><li><div><span style="font-size: 12pt;">最后把子类投入运行环境中， 新逻辑正式投产。</span></div></li><li><div><span style="font-size: 12pt;">放弃修改历史的想法吧，一个项目的基本路径应该是这样的：项目开发、重构、测试、投产、运维，其中的重构可以对原有的设计和代码进行修改，运维尽量减少对原有代码的修改，保持历史代码的纯洁性，提高系统的稳定性。</span></div></li><li><div><span style="font-size: 16px;">在软件设计模式中，不能修改，但可以扩展的思想是最重要的一种设计原则，这就是开放-封闭原则，也叫开-闭原则。</span></div></li><li><div><span style="font-size: 16px;">在做任何系统的时候，都不要指望一开始就需求确定，就再也不会变化，这是不现实也不科学的想法，而既然需求是一定会变化的，开闭原则指导我们：</span></div></li><li><div><span style="font-size: 16px;">在面对需求的变化时，设计的软件可以相对容易的修改，不至于说新需求一来，就要把整个程序推倒重来</span></div></li><li><div><span style="font-size: 16px;">如何设计才能面对需求的改变却可以保持相对稳定，从而使得系统可以在第一个版本之后不断推出新的版本</span></div></li><li><div><span style="font-size: 16px;">设计软件要容易维护又不容易出问题的最好办法就是多扩展，少修改。</span></div></li><li><div><span style="font-size: 12pt;">开放封闭原则的意思就是说，你设计的时候，时刻要考虑，尽量让这个类是足够好，写好了就不要去修改了，如果新需求来，我们增加一些类就完事了，原来的代码能不动则不动。</span></div></li><li><div><span style="font-size: 12pt;">绝对的对修改关闭是不可能的。无论模块是多么的“封闭”，都会存在一些无法对之封闭的变化。既然不可能完全封闭，设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。他必须先猜测出最有可能发生的变化种类，然后构造抽象来隔离那些变化。</span></div></li><li><div><span style="font-size: 12pt;">我们是很难预先猜测，但我们却可以在发生小变化时，就及早去想办法应对发生更大变化的可能。也就是说，等到变化发生时立即采取行动。</span></div></li><li><div><span style="font-size: 12pt;">在我们最初编写代码时，假设变化不会发生。当变化发生时，我们就创建抽象来隔离以后发生的同类变化。</span></div></li><li><div><span style="font-size: 12pt;">面对需求，对程序的改动是通过增加新代码进行的，而不是更改现有的代码。</span><span style="font-size: 12pt;">这就是‘开放封闭原则”的精神所在。</span></div></li><li><div><span style="font-size: 12pt;">并不是什么时候应对变化都是容易的。我们希望的是在开发工作展开不久就知道可能发生的变化。查明可能发生的变化所等待的时间越长，要创建正确的抽象就越困难。</span></div></li><li><div><span style="font-size: 12pt;">开放封闭原则是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术所声称的巨大好处，也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现出频繁变化的那些部分做岀抽象，然而，对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。</span></div></li><li><div><span style="font-size: 16px;">使用开闭原则的理由：</span></div></li><li><div><span style="font-size: 12pt;">首先，开闭原则非常著名，只要是做面向对象编程的，甭管是什么语言，在开发时都会提及开闭原则。</span></div></li><li><div><span style="font-size: 12pt;">其次，开闭原则是最基础的一个原则，其他</span><span style="font-size: 12pt;">原则都是开闭原则的具体形态，也就是说其他五个原则就是指导设计的工具和方法，而开闭原则才是其精神领袖。</span></div></li><li><div><span style="font-size: 16px;">最后，开闭原则是最重要的。</span></div></li><li><div><span style="font-size: 16px;">开闭原则</span><span style="font-size: 12pt;">对测试的影响</span></div></li></ul><div style="margin-left: 120px;"><span style="font-size: 12pt;">有变化提出时，我们就需要考虑一下，原有的健壮代码是否可以不修改，仅仅通过扩展实现变化呢？否则，就需要把原有的测试过程回笼一遍，需要进行单元测试、功能测试、集成测试甚至是验收测试，现在虽然在大力提倡自动化测试工具，但是仍然代替不了人工的测试工作。</span></div><ul><li><div><span style="font-size: 12pt;">开闭原则可以提高复用性</span></div></li></ul><div style="margin-left: 120px;"><span style="font-size: 12pt;">在面向对象的设计中，所有的逻辑都是从原子逻辑组合而来的，而不是在一个类中独立实现一个业务逻辑。只有这样代码才可以复用，粒度越小，被复用的可能性就越大。那为什么要复用呢？减少代码量，避免相同的逻辑分散在多个角落。</span><span style="font-size: 12pt;">怎么才能提高复用率呢？缩小逻辑粒度，直到一个逻辑不可再拆分为止。</span></div><ul><li><div><span style="font-size: 16px;">开闭原则课提高可维护性</span></div></li></ul><div style="margin-left: 120px;"><span style="font-size: 12pt;">维护人员最乐意做的事情就是扩展一个类，而不是修改一个类，甭管原有的代码写得多么优秀还是多么糟糕，让维护人员读懂原有的代码，然后再修改，是一件很痛苦的事情。</span></div><ul><li><div><span style="font-size: 16px;">面向对象的开发要求</span></div></li></ul><div style="margin-left: 120px;"><span style="font-size: 12pt;">万物皆对象，我们需要把所有的事物都抽象成对象，然后针对对象进行操作。</span><span style="font-size: 12pt;">这就需要在设计之初考虑到所有可能变化的因素，然后留下接口，等待“可能”转变为“现实”。</span></div><ul><li><div><span style="font-size: 12pt;">开闭原则是一个非常虚的原则，前面5个原则是对开闭原则的具体解释，但是开闭原则并不局限于这么多，它“虚”得没有边界。</span></div></li><li><div><span style="font-size: 16px;">如何应用开闭原则：</span></div></li><li><div><span style="font-size: 16px;">抽象约束</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">抽象是对一组事物的通用描述，没有具体的实现，也就表示它可以有非常多的可能性，可以跟随需求的变化而变化。因此，通过接口或抽象类可以约束一组可能变化的行为，并且能够实现对扩展开放，其包含三层含义：</span></div><ul><li><div><span style="font-size: 12pt;">第一，通过接口或抽象类约束扩展，对扩展进行边界限定，不允许出现在接口或抽象类中不存在的public方法；</span></div></li><li><div><span style="font-size: 12pt;">第二，参数类型、引用对象尽量使用接口或者抽象类，而不是实现类；</span></div></li><li><div><span style="font-size: 12pt;">第三，抽象层尽量保持稳定，一旦确定即不允许修改。</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">要实现对扩展开放，首要的前提条件就是抽象约束。</span></div><ul><li><div><span style="font-size: 16px;">元数据控制模块行为</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">尽量使用元数据来控制程序的行为，减少重复开发。</span></div><div style="margin-left: 80px;"><span style="font-size: 16px;">元数据：</span><span style="font-size: 12pt;">用来描述环境和数据的数据，通俗地说就是配置参数，参数可以从文件中获得，也可以从数据库中获得。</span></div><ul><li><div><span style="font-size: 16px;">制定项目章程</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">在一个团队中，建立项目章程是非常重要的，因为章程中指定了所有人员都必须遵守的约定，对项目来说，约定优于配置。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">需要项目内约束，每个项目成员都必须遵守，该方法需要一个团队有较高的自觉性，需要一个较长时间的磨合，一旦项目成员都熟悉这样的规则，比通过接口或抽象类进行约束效率更高，而且扩展性一点也没有减少。</span></div><ul><li><div><span style="font-size: 16px;">封装变化</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">对变化的封装包含两层含义：</span></div><ul><li><div><span style="font-size: 12pt;">第一，将相同的变化封装到一个接口或抽象类中；</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">第二，将不同的变化封装到不同的接口或抽象类中，不应该有两个不同的变化出现在同一个接口或抽象类中。</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">封装变化，也就是受保护的变化（protectedvariations），找出预计有变化或不稳定的点，我们为这些变化点创建稳定的接口，准确地讲是封装可能发生的变化。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">23个设计模式都是从各个不同的角度对变化进行封装的。</span></div><ul><li><div><span style="font-size: 12pt;">开闭原则是6大设计原则的重中之重，是最基础的原则，是其他6大原则的精神领袖。</span></div></li><li><div><span style="font-size: 16px;">使用开闭原则时要注意的几个问题：</span></div></li><li><div><span style="font-size: 12pt;">开闭原则也只是一个原则</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">开闭原则只是精神口号，实现拥抱变化的方法非常多，并不局限于这6大设计原则，但是遵循这6大设计原则基本上可以应对大多数变化。因此，我们在项目中应尽量采用这6大原则，适当时候可以进行扩充。</span></div><div style="margin-left: 80px;"><span style="font-size: 16px;">也就是说，原则是死的，人是活的，要根据具体情况灵活运用。</span></div><ul><li><div><span style="font-size: 12pt;">项目规章非常重要</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">如果你是一位项目经理或架构师，应尽量让自己的项目成员稳定，稳定后才能建立高效的团队文化，章程是一个团队所有成员共同的知识结晶，也是所有成员必须遵守的约定。优秀的章程能带给项目带来非常多的好处，如提高开发效率、降低缺陷率、提高团队士气、提高技术成员水平，等等。</span></div><ul><li><div><span style="font-size: 12pt;">预知变化</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">在实践中过程中，架构师或项目经理一旦发现有发生变化的可能，或者变化曾经发生过，则需要考虑现有的架构是否可以轻松地实现这一变化。架构师设计一套系统不仅要符合现有的需求，还要适应可能发生的变化，这才是一个优良的架构。</span></div><ul><li><div><span style="font-size: 12pt;">开闭原则是一个终极目标，任何人包括大师级人物都无法百分之百做到，但朝这个方向努力，可以非常显著地改善一个系统的架构，真正做到“拥抱变化”。</span></div></li><li><div><span style="font-size: 12pt;">在软件开发中，一般不把对配置文件的修改认为是对系统源代码的修改。如果一个系统在扩展时只涉及到修改配置文件，而原有代码没有做任何修改，该系统即可认为是一个符合开闭原则的系统。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">三、里氏代换原则</span></div><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px;">定义</span></span></div></li><li><div><span style="font-size: 12pt;">严格表述：如果对每一个类型为S的对象o1，都有类型为T的对象o2，使得以T定义的所有程序P在所有的对象o1代换o2时，程序P的行为没有变化，那么类型S是类型T的子类型。</span></div></li><li><div><span style="font-size: 16px;">通俗版定义：</span><span style="font-size: 12pt;">所有引用基类（父类）的地方必须能透明地使用其子类的对象。</span></div></li><li><div><span style="font-size: 16px;">一个软件实体如果使用的是一个父类的话，那么一定适用于其子类，而且它察觉不出父类对象和子类对象的区别。也就是说，在软件里面，把父类都替换成它的子类，程序的行为没有变化。</span></div></li><li><div><span style="font-size: 12pt;">里式替换原则是用来指导，继承关系中子类该如何设计的一个原则。</span></div></li><li><div><span style="font-size: 12pt;">子类对象（object of subtype/derived class）能够替换程序（program）中父类对象（object of base/parent class）出现的任何地方，并且保证原来程序的逻辑行为（behavior）不变及正确性不被破坏。</span></div></li><li><div><span style="font-size: 12pt;">实际上，里式替换原则还有另外一个更加能落地、更有指导意义的描述，那就是“Design By Contract”，中文翻译就是“按照协议来设计”。</span></div><div><span style="font-size: 12pt;">子类在设计的时候，要遵守父类的行为约定（或者叫协议）。父类定义了函数的行为约定，那子类可以改变函数的内部实现逻辑，但不能改变函数原有的行为约定。这里的行为约定包括：函数声明要实现的功能；对输入、输出、异常的约定；甚至包括注释中所罗列的任何特殊说明。实际上，定义中父类和子类之间的关系，也可以替换成接口和实现类之间的关系。</span></div></li><li><div><span style="font-size: 12pt;">判断子类的设计实现是否违背里式替换原则，还有一个小窍门，那就是拿父类的单元测试去验证子类的代码。如果某些单元测试运行失败，就有可能说明，子类的设计实现没有完全地遵守父类的约定，子类有可能违背了里式替换原则。</span></div></li><li><div><span style="font-size: 16px;">简单的说，子类型必须能够替换掉他们的父类。</span></div></li><li><div><span style="font-size: 16px;">子类继承父类，所以子类可以以父类的身份出现。</span></div></li><li><div><span style="font-size: 12pt;">里氏代换原则告诉我们，在软件中将一个基类对象替换成它的子类对象，程序将不会产生任何错误和异常，即</span><span style="font-size: 16px; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; color: rgb(0, 0, 0); font-family: 微软雅黑; font-variant-caps: normal; font-variant-ligatures: normal;">只要父类能出现的地方子类就可以出现，</span><span style="font-size: 16px; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; color: rgb(0, 0, 0); font-family: 微软雅黑; font-variant-caps: normal; font-variant-ligatures: normal;">使用者可能根本就不需要知道是父类还是子类。</span><span style="font-size: 12pt;">反过来则不成立，如果一个软件实体使用的是一个子类对象的话，那么它不一定能够使用基类对象，即</span><span style="font-size: 16px; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; color: rgb(0, 0, 0); font-family: 微软雅黑; font-variant-caps: normal; font-variant-ligatures: normal;">有子类出现的地方，父类未必就能适应。</span></div></li><li><div><span style="font-size: 12pt;">虽然从定义描述和代码实现上来看，多态和里式替换有点类似，但它们关注的角度是不一样的。多态是面向对象编程的一大特性，也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里式替换是一种设计原则，是用来指导继承关系中子类该如何设计的，子类的设计要保证在替换父类的时候，不改变原有程序的逻辑以及不破坏原有程序的正确性。</span></div></li><li><div><span style="font-size: 16px;">正因为有了里氏代换原则，使得继承复用成为了可能，只有当子类可以替换掉父类，软件单位的功能不受到影响时，父类才能真正被复用，而子类也能够在父类的基础上增加新的行为。</span></div></li><li><div><span style="font-size: 16px;">正因为有了里氏代换原则，才使得开放-封闭成为可能。正是由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展。</span><span style="font-size: 12pt; color: unset; font-family: unset;">里氏代换原则是实现开闭原则的重要方式之一，由于使用基类对象的地方都可以使用子类对象，因此在程序中尽量使用基类类型来对对象进行定义，而在运行时再确定其子类类型，用子类对象来替换父类对象。</span></div></li><li><div><span style="font-size: 16px;">优缺点</span></div></li><li><div><span style="font-size: 12pt;">在面向对象的语言中，继承是必不可少的、非常优秀的语言机制，它有如下优点：</span></div></li><li><div><span style="font-size: 12pt;">代码共享， 减少创建类的工作量， 每个子类都拥有父类的方法和属性；</span></div></li><li><div><span style="font-size: 12pt;">提高代码的重用性；</span></div></li><li><div><span style="font-size: 12pt;">子类可以形似父类， 但又异于父类；</span></div></li><li><div><span style="font-size: 12pt;">提高代码的可扩展性，实现父类的方法就可以“为所欲为”了，很多开源框架的扩展接口都是通过继承父类来完成的；</span></div></li><li><div><span style="font-size: 12pt;">提高产品或项目的开放性。</span></div></li><li><div><span style="font-size: 16px;">继承的缺点如下</span></div></li><li><div><span style="font-size: 12pt;">继承是侵入性的。只要继承，就必须拥有父类的所有属性和方法；</span></div></li><li><div><span style="font-size: 12pt;">降低代码的灵活性。子类必须拥有父类的属性和方法，让子类自由的世界中多了些约束；</span></div></li><li><div><span style="font-size: 12pt;">增强了耦合性。当父类的常量、变量和方法被修改时，需要考虑子类的修改，而且在缺乏规范的环境下，这种修改可能带来非常糟糕的结果——大段的代码需要重构。</span></div></li><li><div><span style="font-size: 12pt;">从整体上来看，继承利大于弊，如何让“利”的因素发挥最大的作用，同时减少“弊”带来的麻烦，解决方案是引入里氏替换原则。</span></div></li><li><div><span style="font-size: 12pt;">在使用里氏代换原则时需要注意如下几个问题：</span></div></li><li><div><span style="font-size: 12pt;">子类的所有方法必须在父类中声明，或子类必须实现父类中声明的所有方法。根据里氏代换原则，为了保证系统的扩展性，在程序中通常使用父类来进行定义，如果一个方法只存在子类中，在父类中不提供相应的声明，则无法在以父类定义的对象中使用该方法。</span></div></li><li><div><span style="font-size: 12pt;">我们在运用里氏代换原则时，尽量把父类设计为抽象类或者接口，让子类继承父类或实现父接口，并实现在父类中声明的方法，运行时，子类实例替换父类实例，我们可以很方便地扩展系统的功能，同时无须修改原有子类的代码，增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。</span></div></li><li><div><span style="font-size: 12pt;">Java语言中，在编译阶段，Java编译器会检查一个程序是否符合里氏代换原则，这是一个与实现无关的、纯语法意义上的检查，但Java编译器的检查是有局限的。</span></div></li><li><div><span style="font-size: 12pt;">在定义成员变量、定义局部变量、确定方法返回类型时都可使用里氏代换原则。针对基类编程，在程序运行时再确定具体子类。</span></div></li><li><div><span style="font-size: 12pt;">里氏替换原则为良好的继承定义了一个规范，一句简单的定义包含了4层含义。</span></div></li><li><div><span style="font-size: 12pt;">子类必须完全实现父类的方法</span></div></li><li><div><span style="font-size: 12pt;">我们在做系统设计时，经常会定义一个接口或抽象类，然后编码实现，调用类则直接传入接口或抽象类，其实这里已经使用了里氏替换原则。</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">在类中调用其他类时务必要使用父类或接口，如果不能使用父类或接口，则说明类的设计已经违背了LSP原则。</span></div></li><li><div><span style="font-size: 16px;">Java的三大特征是封装、继承、多态。继承就是告诉你拥有父类的方法和属性，然后可以重写父类的方法，按继承原则，在具体应用场景中要考虑子类是否能够完整的实现父类的业务。</span></div></li><li><div><span style="font-size: 12pt;">如果子类不能完整地实现父类的方法，或者父类的某些方法在子类中已经发生“畸变”，则建议断开父子继承关系，采用依赖、聚集、组合等关系代替继承。</span></div></li><li><div><span style="font-size: 12pt;">子类可以有自己的个性</span></div></li><li><div><span style="font-size: 12pt;">子类当然可以有自己的行为和外观了，也就是方法和属性，但里氏替换原则可以正着用，但是不能反过来用。在子类出现的地方，父类未必就可以胜任。</span></div></li><li><div><span style="font-size: 12pt;">这也是大家经常说的向下转型（downcast）是不安全的，从里氏替换原则来看，就是有子类出现的地方父类未必就可以出现。</span></div></li><li><div><span style="font-size: 12pt;">覆盖或实现父类的方法时输入参数可以被放大</span></div></li><li><div><span style="font-size: 12pt;">方法中的输入参数称为前置条件，</span><span style="font-size: 12pt;">里氏替换原则也要求制定一个契约，就是父类或接口，这种设计方法也叫做DesignbyContract（契约设计），</span><span style="font-size: 12pt;">契约制定了，也就同时制定了前置条件和后置条件，前置条件就是你要让我执行，就必须满足我的条件；后置条件就是我执行完了需要反馈，标准是什么。</span></div></li><li><div><span style="font-size: 16px;">重载和覆写</span></div></li><li><div><span style="font-size: 16px;">重载：子类和父类方法名相同，但方法的输入参数不同。【Java中似乎没有覆盖的说法，当子类和父类方法名相同，输入参数不同的时候，子类不会隐藏父类的方法，只是简单的重载父类的方法，两者都可以使用】</span></div></li><li><div><span style="font-size: 16px;">覆写：子类与父类方法名相同，方法的输入参数也相同，这是多态，虚函数的覆写。</span></div></li><li><div><span style="font-size: 16px;">如想要里氏代换原则成立（即父类可以用子类代替），子类应覆写父类的方法，也可以重载这个方法，前提是子类要扩大方法的前置条件，就是输入参数的类型宽于父类的类型覆盖范围。</span></div></li><li><div><span style="font-size: 12pt;">子类在没有覆写父类的方法的前提下，子类方法被执行了，这会引起业务逻辑混乱，因为在实际应用中父类一般都是抽象类，子类是实现类，你传递一个这样的实现类就会“歪曲”了父类的意图，引起一堆意想不到的业务逻辑混乱，所以子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或者更宽松。</span></div></li><li><div><span style="font-size: 12pt;">覆写或实现父类的方法时输出结果可以被缩小</span></div></li><li><div><span style="font-size: 12pt;">父类的一个方法的返回值是一个类型T，子类的相同方法（重载或覆写）的返回值为S，那么里氏替换原则就要求S必须小于等于T，也就是说，要么S和T是同一个类型，要么S是T的子类。</span></div></li><li><div><span style="font-size: 12pt;">如果是覆写，父类和子类的同名方法的输入参数是相同的，两个方法的范围值S小于等于T，这是覆写的要求。</span></div></li><li><div><span style="font-size: 12pt;">如果是重载，则要求方法的输入参数类型或数量不相同，在里氏替换原则要求下，写的这个方法是不会被调用的。</span></div></li><li><div><span style="font-size: 12pt;">采用里氏替换原则的目的就是增强程序的健壮性，版本升级时也可以保持非常好的兼容性。即使增加子类，原有的子类还可以继续运行。</span></div></li><li><div><span style="font-size: 12pt;">在实际项目中，每个子类对应不同的业务含义，使用父类作为参数，传递不同的子类完成不同的业务逻辑。</span></div></li><li><div><span style="font-size: 12pt;">在项目中，采用里氏替换原则时，尽量避免子类的“个性”，一旦子类有“个性”，这个子类和父类之间的关系就很难调和了，把子类当做父类使用，子类的“个性”被抹杀；把子类单独作为一个业务来使用，则会让代码间的耦合关系变得扑朔迷离，不满足开闭原则的要求。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">四、依赖倒转原则</span></div><ul><li><div><span style="font-size: 16px;">定义</span></div></li><li><div><span style="font-size: 16px;">高层模块不应该依赖低层模块，两个都应该依赖抽象。</span></div></li><li><div><span style="font-size: 16px;">抽象不应该依赖细节，细节应该依赖抽象。</span></div></li><li><div><span style="font-size: 16px;">针对接口编程，不要针对实现编程。</span></div></li><li><div><span style="font-size: 16px;">代码里减少对于具体类的依赖是件好事。</span></div></li><li><div><span style="font-size: 16px;">解释</span></div></li><ul><li><div><span style="font-size: 16px;">高层模块和低层模块</span></div></li></ul></ul><div style="margin-left: 80px;"><div><span style="font-size: 12pt;">高层模块和低层模块容易理解，每一个逻辑的实现都是由原子逻辑组成的，不可分割的原子逻辑就是低层模块，原子逻辑的再组装就是高层模块。</span></div><div><span style="font-size: 12pt;">所谓高层模块和低层模块的划分，简单来说就是，在调用链上，调用者属于高层，被调用者属于低层。</span></div><div><span style="font-size: 16px;">高层组件是由其他低层组件定义其行为的类。</span></div></div><ul><ul><li><div><span style="font-size: 16px;">抽象和细节</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">在Java语言中，抽象就是指接口或抽象类，两者都是不能直接被实例化的；细节就是实现类，实现接口或继承抽象类而产生的类就是细节，其特点就是可以直接被实例化，也就是可以加上一个关键字new产生一个对象。</span></div><ul><ul><li><div><span style="font-size: 16px; color: unset; font-family: unset;">倒置</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 16px; color: unset; font-family: unset;">倒置指的是和一般OO设计的思考方式完全相反。低层组件依赖高层的抽象，高层组件也依赖相同的抽象。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">先说“正置”是什么意思，依赖正置就是类间的依赖是实实在在的实现类间的依赖，也就是面向实现编程，这也是正常人的思维方式。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">而编写程序需要的是对现实世界的事物进行抽象，抽象的结果就是有了抽象类和接口，然后我们根据系统设计的需要产生了抽象间的依赖，代替了人们传统思维中的事物间的依赖，“倒置”就是从这里产生的。</span></div><ul><li><div><span style="font-size: 12pt;">如果说开闭原则是面向对象设计的目标的话，那么依赖倒转原则就是面向对象设计的主要实现机制之一，它是系统抽象化的具体实现。</span></div></li><li><div><span style="font-size: 16px;">依赖倒置原则在Java语言中的表现：</span></div></li><li><div><span style="font-size: 12pt;">模块间的依赖通过抽象发生，实现类之间不发生直接的依赖关系，其依赖关系是通过接口或抽象类产生的；</span></div></li><li><div><span style="font-size: 16px;">接口或者抽象类不依赖于实现类；</span></div></li><li><div><span style="font-size: 16px;">实现类依赖接口或抽象类。</span></div></li><li><div><span style="font-size: 16px;">实际上，依赖倒置原则更加精简的定义就是“面向接口编程”。</span></div></li><li><div><span style="font-size: 12pt;">采用依赖倒置原则可以减少类间的耦合性，提高系统的稳定性，降低并行开发引起的风险，提高代码的可读性和可维护性。</span></div></li><li><div><span style="font-size: 16px;">提高系统稳定性</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">设计是否具备稳定性，只要适当地“松松土”，观察“设计的蓝图”是否还可以茁壮地成长就可以得出结论，稳定性较高的设计，在周围环境频繁变化的时候，依然可以做到“我自岿然不动”。</span></div><ul><li><div><span style="font-size: 12pt;">降低并行开发引起的风险</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">并行开发最大的风险就是风险扩散，本来只是一段程序的错误或异常，逐步波及一个功能，一个模块，甚至到最后毁坏了整个项目。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt; color: unset; font-family: unset;">接口只是一个抽象化的概念，是对一类事物的最抽象描述，具体的实现代码由相应的实现类来完成。如果高层业务逻辑对低层模块的依赖都建立在抽象上，在新增加低层模块时，只修改了业务场景类，也就是高层模块，对其他低层模块不需要做任何修改，业务就可以运行，这就把“变更”引起的风险扩散降到最低。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">两个类之间有依赖关系，只要制定出两者之间的接口（或抽象类）就可以独立开发了，而且项目之间的单元测试也可以独立地运行，而TDD（Test-Driven Development，测试驱动开发）开发模式就是依赖倒置原则的最高级应用。可以引入一个JMock工具，其最基本的功能是根据抽象虚拟一个对象进行测试。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">从这一点来看，两个相互依赖的对象可以分别进行开发，孤立地进行单元测试，进而保证并行开发的效率和质量。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">测试驱动开发，先写好单元测试类，然后再写实现类，这对提高代码的质量有非常大的帮助，特别适合研发类项目或在项目成员整体水平比较低的情况下采用。</span></div><ul><li><div><span style="font-size: 16px;">面向过程开发时，为了使得常用代码可以复用，一般都会把这些常用代码写成许许多多函数的程序库，这样我们在做新项目的时候，去调用这些低层的函数就可以了。这就叫做高层模块依赖低层模块。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 16px;">问题也就出现在这里，我们要做新项目的时候，发现业务逻辑的高层模块都是一样的，但客户却希望使用不同的低层模块，我们希望能再次利用这些高层模块，但高层模块都是与低层绑定在一起的，没有办法复用这些高层模块，这就非常糟糕了。</span></div><div style="margin-left: 40px;"><span style="font-size: 16px;">而如果不管高层模块还是低层模块，它们都依赖于抽象，具体一点就是接口和抽象类，只要接口是稳定的，那么任何一个的更改都不用担心其他受到影响，这就使得无论高层模块还是低层模块都可以很容易的被复用。</span></div><ul><li><div><span style="font-size: 12pt;">依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中，尽量引用层次高的抽象层类，即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明，以及数据类型的转换等，而不要用具体类来做这些事情。为了确保该原则的应用，一个具体类应当只实现接口或抽象类中声明过的方法，而不要给出多余的方法，否则将无法调用到在子类中增加的新方法。</span></div></li><li><div><span style="font-size: 12pt;">抽象是对实现的约束，对依赖者而言，也是一种契约，不仅仅约束自己，还同时约束自己与外部的关系，其目的是保证所有的细节不脱离契约的范畴，确保约束双方按照既定的契约（抽象）共同发展，只要抽象这根基线在，细节就脱离不了这个圈圈，始终让你的对象做到“言必信，行必果”。</span></div></li><li><div><span style="font-size: 16px;">依赖是可以传递的，但只要做到抽象依赖，即使是多层的依赖传递也无所畏惧。</span></div></li><li><div><span style="font-size: 12pt;">在实现依赖倒转原则时，我们需要针对抽象层编程，而将具体类的对象通过依赖注入(DependencyInjection,DI)的方式注入到其他对象中，依赖注入是指当一个对象要与其他对象发生依赖关系时，通过抽象来注入所依赖的对象。</span><span style="font-size: 16px;">对象的依赖关系有三种方式传递：</span></div></li><li><div><span style="font-size: 16px;">构造注入，</span><span style="font-size: 12pt;">通过构造函数来传入具体类的对象</span></div></li><li><div><span style="font-size: 12pt;">在类中通过构造函数声明依赖对象</span><span style="font-size: 12pt;">。</span></div></li><li><div><span style="font-size: 12pt;">设置注入，</span><span style="font-size: 12pt;">通过Setter方法来传入具体类的对象</span></div></li><li><div><span style="font-size: 12pt;">在抽象中设置Setter方法声明依赖关系。</span></div></li><li><div><span style="font-size: 12pt;">接口注入，接口声明依赖对象</span></div></li><li><div><span style="font-size: 12pt;">在接口中声明的业务方法来传入具体类的对象。</span><span style="font-size: 12pt;">这些方法在定义时使用的是抽象类型，在运行时再传入具体类型的对象，由子类对象来覆盖父类对象。</span></div></li><li><div><span style="font-size: 16px;">依赖倒转其实可以说是面向对象设计的标志，用哪种语言来编写程序不重要，如果编写时考虑的都是如何针对抽象编程而不是针对细节编程，即程序中所有的依赖关系都是终止于抽象类或者接口，那就是面向对象的设计，反之那就是过程化的设计。</span></div></li><li><div><span style="font-size: 16px;">依赖倒置原则的本质就是通过抽象（接口或者抽象类）使各个类或模块的实现彼此独立，不互相影响，实现模块间的松耦合。</span></div></li><li><div><span style="font-size: 16px;">在项目中应用依赖倒置原则需要遵循：</span></div></li><li><div><span style="font-size: 12pt;">每个类尽量都有接口或抽象类，或者抽象类和接口两者都具备</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">这是依赖倒置的基本要求，接口和抽象类都是属于抽象的，有了抽象才可能依赖倒置。</span></div><ul><li><div><span style="font-size: 12pt;">变量的表面类型</span><span style="font-size: 12pt; font-weight: bold;">尽量</span><span style="font-size: 12pt;">是接口或者是抽象类</span></div></li><li><div><span style="font-size: 12pt;">任何类都不应该从具体类派生</span></div></li><li><div><span style="font-size: 16px;">尽量不要覆写基类的方法</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">如果基类是一个抽象类，而且这个方法已经实现了，子类尽量不要覆写。类间依赖的是抽象，覆写了抽象方法，对依赖的稳定性会产生一定的影响。【会产生对结果不确切的预期】</span></div><ul><li><div><span style="font-size: 16px;">结合里氏替换原则使用</span></div></li></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">里氏替换原则要求，父类出现的地方子类就能出现，再结合依赖倒置原则，我们可以得出这样一个通俗的规则：接口负责定义public属性和方法，并且声明与其他对象的依赖关系，抽象类负责公共构造部分的实现，实现类准确的实现业务逻辑，同时在适当的时候对父类进行细化。</span></div><ul><li><div><span style="font-size: 12pt;">依赖倒置原则的优点在小型项目中很难体现出来，</span><span style="font-size: 12pt;">是否采用依赖倒置原则影响不大。但是，在一个大中型项目中，采用依赖倒置原则有非常多的优点，特别是规避一些非技术因素引起的问题。</span></div></li><li><div><span style="font-size: 12pt;">项目越大，需求变化的概率也越大，通过采用依赖倒置原则设计的接口或抽象类对实现类进行约束，可以减少需求变化引起的工作量剧增的情况。</span></div></li><li><div><span style="font-size: 12pt;">人员的变动在大中型项目中也是时常存在的，如果设计优良、代码结构清晰，人员变化对项目的影响基本为零。</span></div></li><li><div><span style="font-size: 12pt;">大中型项目的维护周期一般都很长，采用依赖倒置原则可以让维护人员轻松地扩展和维护。</span></div></li><li><div><span style="font-size: 12pt;">依赖倒置原则是6个设计原则中最难以实现的原则，它是实现开闭原则的重要途径，依赖倒置原则没有实现，就别想实现对扩展开放，对修改关闭。</span></div></li><li><div><span style="font-size: 12pt;">在项目中，大家只要记住是“面向接口编程”就基本上抓住了依赖倒置原则的核心。</span></div></li><li><div><span style="font-size: 12pt;">在引入抽象层后，系统将具有很好的灵活性，在程序中尽量使用抽象层进行编程，而将具体类写在配置文件中，这样一来，如果系统行为发生变化，只需要对抽象层进行扩展，并修改配置文件，而无须修改原有系统的源代码，在不修改的情况下来扩展系统的功能，满足开闭原则的要求。</span></div></li><li><div><span style="font-size: 12pt;">在大多数情况下，开闭原则、里氏代换原则、依赖倒转原则这三个设计原则会同时出现，开闭原则是目标，里氏代换原则是基础，依赖倒转原则是手段，它们相辅相成，相互补充，目标一致，只是分析问题时所站角度不同而已。</span></div></li><li><div><span style="font-size: 16px;">然而，</span><span style="font-size: 12pt;">在实际的项目中使用依赖倒置原则时需要审时度势，不要抓住一个原则不放，每一个原则的优点都是有限度的，并不是放之四海而皆准的真理，</span><span style="font-size: 12pt;">作为一个项目经理或架构师，应该懂得技术只是实现目的的工具。不要过分追求依赖倒置原则。</span></div><div><span style="font-size: 12pt;">正如同我们的许多原则一样，应该尽量达到这个原则，而不是随时都要遵循这个原则。我们都很清楚，任何Java程序都有违反这些指导方针的地方！</span></div><div><span style="font-size: 12pt;">但是，如果你深入体验这些方针，将这些方针内化成你思考的一部分，那么在设计时，你将知道何时有足够的理由违反这样的原则比方说，如果有一个不像是会改变的类，那么在代码中直接实例化具体类也就没什么大碍。</span></div><div><span style="font-size: 12pt;">另一方面，如果有个类可能改变，你可以采用一些好技巧（例如工厂方法）来封装改变。</span></div></li><li><div><span style="font-size: 16px;">以下指导方针可以帮助避免在OO设计中违反依赖倒置原则</span></div></li><ul><li><div><span style="font-size: 16px;">变量不可以持有具体类的引用</span></div><div><span style="font-size: 12pt;">如果使用new，就会持有具体类的引用。你可以改用工厂来避开这样的做法。【不直接new一个对象】</span></div></li><li><div><span style="font-size: 12pt;">不要让类派生自具体类</span></div><div><span style="font-size: 12pt;">如果派生自具体类，你就会依赖具体类。请派生自一个抽象（接口或抽象类）。【具体类不做父类】</span></div></li><li><div><span style="font-size: 12pt;">不要覆盖基类中已实现的方法</span></div><div><span style="font-size: 12pt;">如果覆盖基类已实现的方法，那么你的类就不是一个真正适合被继承的抽象。</span><span style="font-size: 12pt; color: unset; font-family: unset;">基类中已实现的方法，应该由所有的子类共享。【虚函数不是覆盖】</span></div></li></ul></ul><div><span style="font-size: 12pt; font-weight: bold;">五、接口隔离原则</span></div><ul><li><div><span style="font-size: 12pt;">定义</span></div></li><li><div><span style="font-size: 12pt;">客户端不应该依赖它不需要的接口。</span></div></li><li><div><span style="font-size: 12pt;">类间的依赖关系应该建立在最小的接口上。</span></div></li><li><div><span style="font-size: 12pt;">使用多个专门的接口，而不使用单一的总接口。</span></div></li><li><div><span style="font-size: 16px;">对定义的理解</span></div></li><li><div><span style="font-size: 12pt;">客户端不应该依赖它不需要的接口</span></div></li><li><div><span style="font-size: 12pt;">依赖它需要的接口，客户端需要什么接口就提供什么接口，把不需要的接口剔除掉</span></div></li><li><div><span style="font-size: 12pt;">需要对接口进行细化，保证其纯洁性；</span></div></li><li><div><span style="font-size: 12pt;">类间的依赖关系应该建立在最小的接口上</span></div></li><li><div><span style="font-size: 12pt;">要求是最小的接口，也是要求接口细化，接口纯洁。</span></div></li><li><div><span style="font-size: 12pt;">建立单一接口，不要建立臃肿庞大的接口。</span></div></li><li><div><span style="font-size: 12pt;">接口尽量细化，同时接口中的方法尽量少。</span></div></li><li><div><span style="font-size: 12pt;">当一个接口太大时，我们需要将它分割成一些更细小的接口，使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色，不干不该干的事，该干的事都要干。</span></div></li><li><div><span style="font-size: 12pt;">接口分为两种：</span></div></li><li><div><span style="font-size: 12pt;">实例接口（Object Interface）</span><span style="font-size: 12pt;">【实际上就是一个具体类】</span></div></li><li><div><span style="font-size: 12pt;">在Java中声明一个类，然后用new关键字产生一个实例，它是对一个类型的事物的描述，这是一种接口。</span></div></li><li><div><span style="font-size: 12pt;">指一个类型所具有的方法特征的集合，仅仅是一种逻辑上的抽象。</span></div></li><li><div><span style="font-size: 12pt;">Java中的类也是一种接口。</span></div></li><li><div><span style="font-size: 12pt;">当把“接口”理解成一个类型所提供的所有方法特征的集合的时候，这就是一种逻辑上的概念，接口的划分将直接带来类型的划分。</span></div></li><li><div><span style="font-size: 12pt;">可以把接口理解成角色，一个接口只能代表一个角色，每个角色都有它特定的一个接口，此时，这个原则可以叫做“角色隔离原则”。</span></div></li><li><div><span style="font-size: 12pt;">类接口（Class Interface）</span></div></li><li><div><span style="font-size: 12pt;">Java中经常使用的interface关键字定义的接口。</span></div></li><li><div><span style="font-size: 12pt;">指某种语言具体的“接口”定义，有严格的定义和结构。</span></div></li><li><div><span style="font-size: 12pt;">接口仅仅提供客户端需要的行为，客户端不需要的行为则隐藏起来</span></div></li><li><div><span style="font-size: 12pt;">应当为客户端提供尽可能小的单独的接口，而不要提供大的总接口。在面向对象编程语言中，实现一个接口就需要实现该接口中定义的所有方法，因此大的总接口使用起来不一定很方便</span></div></li><li><div><span style="font-size: 12pt;">为了使接口的职责单一，需要将大接口中的方法根据其职责不同分别放在不同的小接口中，以确保每个接口使用起来都较为方便，并都承担某一单一角色</span></div></li><li><div><span style="font-size: 16px;">与单一职责原则的区别</span></div></li><li><div><span style="font-size: 12pt;">接口隔离原则与单一职责的审视角度是不相同的</span></div></li><li><div><span style="font-size: 12pt;">单一职责要求的是类和接口职责单一，注重的是职责，这是业务逻辑上的划分。</span></div></li><li><div><span style="font-size: 12pt;">接口隔离原则要求接口的方法尽量少。</span></div></li><li><div><span style="font-size: 12pt;">接口隔离原则要求“尽量使用多个专门的接口”。</span></div></li><li><div><span style="font-size: 12pt;">专门</span><span style="font-size: 12pt;">就是指提供给每个模块的都应该是单一接口，提供给几个模块就应该有几个接口，而不是建立一个庞大的臃肿的接口，容纳所有的客户端访问。</span></div></li><li><div><span style="font-size: 12pt;">接口是我们设计时对外提供的契约，通过分散定义多个接口，可以预防未来变更的扩散，提高系统的灵活性和可维护性。</span></div></li><li><div><span style="font-size: 12pt;">接口隔离原则是对接口进行规范约束，其包含以下4层含义：</span></div></li><li><div><span style="font-size: 12pt;">接口要尽量小</span></div></li><li><div><span style="font-size: 12pt;">这是接口隔离原则的核心定义，不出现臃肿的接口（FatInterface）</span></div></li><li><div><span style="font-size: 12pt;">“小”是有限度的</span></div></li><li><div><span style="font-size: 12pt;">根据接口隔离原则拆分接口时，首先必须满足单一职责原则</span></div></li><li><div><span style="font-size: 12pt;">接口要高内聚</span></div></li><li><div><span style="font-size: 12pt;">高内聚就是提高接口、类、模块的处理能力，减少对外的交互</span></div></li><li><div><span style="font-size: 12pt;">具体到接口隔离原则就是，要求在接口中尽量少公布public方法，接口是对外的承诺，承诺越少对系统的开发越有利，变更的风险也就越少，同时也有利于降低成本。</span></div></li><li><div><span style="font-size: 16px;">定制服务</span></div></li><li><div><span style="font-size: 12pt;">一个系统或系统内的模块之间必然会有耦合，有耦合就要有相互访问的接口</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">在做系统设计时需要考虑对系统之间或模块之间的接口采用定制服务。</span></div></li><li><div><span style="font-size: 12pt;">采用定制服务就必然有一个要求：只提供访问者需要的方法</span></div></li><li><div><span style="font-size: 16px; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; color: rgb(0, 0, 0); font-family: 微软雅黑; font-variant-caps: normal; font-variant-ligatures: normal;">接口应该尽量细化，同时接口中的方法应该尽量少，每个接口中只包含一个客户端（如子模块或业务逻辑类）所需的方法即可，这种机制称为“定制服务”，即为不同的客户端提供宽窄不同的接口。</span></div></li><li><div><span style="font-size: 16px;">接口设计是有限度的</span></div></li><li><div><span style="font-size: 12pt;">接口的设计粒度越小，系统越灵活，这是不争的事实</span></div></li><li><div><span style="font-size: 16px;">但</span><span style="font-size: 12pt;">灵活的同时也带来了结构的复杂化，开发难度增加，可维护性降低</span></div></li><li><div><span style="font-size: 12pt;">接口设计一定要适度，但对于“度”如何判断，根据经验和常识判断，没有一个固化或可测量的标准</span></div></li><li><div><span style="font-size: 12pt;">需要注意控制接口的粒度，接口不能太小，如果太小会导致系统中接口泛滥，不利于维护；接口也不能太大，太大的接口将违背接口隔离原则，灵活性较差，使用起来很不方便。一般而言，接口中仅包含为某一类用户定制的方法即可，不应该强迫客户依赖于那些它们不用的方法。</span></div></li><li><div><span style="font-size: 12pt;">接口隔离原则是对接口的定义，同时也是对类的定义，接口和类尽量使用原子接口或原子类来组装</span></div></li><li><div><span style="font-size: 12pt;">接口隔离原则在实践中可以根据以下几个原则进行：</span></div></li><li><div><span style="font-size: 12pt;">一个接口只服务于一个子模块或业务逻辑</span></div></li><li><div><span style="font-size: 12pt;">通过业务逻辑压缩接口中的public方法，接口时常去回顾，尽量让接口达到精简，而不是肥胖臃肿</span></div></li><li><div><span style="font-size: 12pt;">已经被污染了的接口，尽量去修改，若变更的风险较大，则采用适配器模式进行转化处理</span></div></li><li><div><span style="font-size: 12pt;">了解环境，拒绝盲从。</span><span style="font-size: 12pt;">每个项目或产品都有特定的环境因素，</span><span style="font-size: 12pt;">环境不同，接口拆分的标准就不同。应深入了解业务逻辑，针对性的使用接口隔离原则。</span></div></li><li><div><span style="font-size: 12pt;">接口隔离原则和其他设计原则一样，都需要花费较多的时间和精力来进行设计和筹划，但是它带来了设计的灵活性</span></div></li><li><div><span style="font-size: 12pt;">怎么才能正确地使用接口隔离原则呢？答案是根据经验和常识决定接口的粒度大小，接口粒度太小，导致接口数据剧增，但如果</span><span style="font-size: 12pt; color: unset; font-family: unset;">接口粒度太大，灵活性降低，无法提供定制服务，给整体项目带来无法预料的风险</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">六、迪米特法则</span></div><ul><li><div><span style="font-size: 12pt;">迪米特法则也称为最少知识原则（Least Knowledge Principle，LKP）</span></div></li><li><div><span style="font-size: 16px;">定义</span></div></li><ul><li><div><span style="font-size: 12pt;">一个对象应该对其他对象有最少的了解。</span></div></li><li><div><span style="font-size: 16px;">一个软件实体应当尽可能少的与其他实体发生相互作用。</span></div></li><li><div><span style="font-size: 12pt;">不要和“陌生人”说话。</span></div></li><li><div><span style="font-size: 12pt;">只与你的直接朋友（密友）通信。</span></div></li><li><div><span style="font-size: 16px;">要减少对象之间的交互，只留下几个“密友”。</span></div></li></ul><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">通俗地讲，一个类应该对自己需要耦合或调用的类知道得最少，你（被耦合或调用的类）的内部是如何复杂都和我没关系，那是你的事情，我就知道你提供的这么多public方法，我就调用这么多，其他的我一概不关心。</span></div></li><li><div><span style="font-size: 16px;">迪米特法则告诉我们，当我们正在设计一个系统，不管是任何对象，都要注意它所交互的类有哪些，并注意它和这些类是如何交互的。</span></div><div><span style="font-size: 16px;">这个原则希望我们在设计中，不要让太多的类耦合在一起，免得修改系统中的一部分，会影响到其他部分。</span></div></li><li><div><span style="font-size: 12pt;">如果一个系统符合迪米特法则，那么当其中某一个模块发生修改时，就会尽量少地影响其他模块，扩展会相对容易，这是对软件实体之间通信的限制，迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度，使类与类之间保持松散的耦合关系。</span></div></li><li><div><span style="font-size: 16px;">在迪米特法则中，对于一个对象，其朋友包括以下几类：</span></div></li><ul><li><div><span style="font-size: 12pt;">当前对象本身(this)；</span></div></li><li><div><span style="font-size: 12pt;">以参数形式传入到当前对象方法中的对象；</span></div></li><li><div><span style="font-size: 12pt;">当前对象的成员对象；</span></div></li><li><div><span style="font-size: 12pt;">如果当前对象的成员对象是一个集合，那么集合中的元素也都是朋友；</span></div></li><li><div><span style="font-size: 12pt;">当前对象所创建的对象。</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">任何一个对象，如果满足上面的条件之一，就是当前对象的“朋友”，否则就是“陌生人”。</span></div><ul><li><div><span style="font-size: 12pt;">在应用迪米特法则时，一个对象只能与直接朋友发生交互，不要与“陌生人”发生直接交互，这样做可以降低系统的耦合度，一个对象的改变不会给太多其他对象带来影响。</span></div></li><li><div><span style="font-size: 12pt;">迪米特法则要求我们在设计系统时，应该尽量减少对象之间的交互，如果两个对象之间不必彼此直接通信，那么这两个对象就不应当发生任何直接的相互作用，如果其中的一个对象需要调用另一个对象的某一个方法的话，可以通过第三者转发这个调用。简言之，就是通过引入一个合理的第三者来降低现有对象之间的耦合度。</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">迪米特法则对类的低耦合提出了明确的要求，其包含以下4层含义：</span></div></li><li><div><span style="font-size: 12pt;">只和朋友交流</span></div></li><li><div><span style="font-size: 16px; color: unset; font-family: unset;">迪米特法则还有一个定义是：</span><span style="font-size: 12pt; color: unset; font-family: unset;">只与直接的朋友通</span><span style="font-size: 12pt; color: unset; font-family: unset;">信。</span></div></li><li><div><span style="font-size: 16px; color: unset; font-family: unset;">直接朋友：</span><span style="font-size: 12pt; color: unset; font-family: unset;">每个对象都必然会与其他对象有耦合关系，两个对象之间的耦合就成为朋友关系，这种关系的类型有很多，例如组合、聚合、依赖等。</span></div></li><li><div><span style="font-size: 12pt;">朋友类的定义是这样的：出现在成员变量、方法的输入输出参数中的类称为成员朋友类，而出现在方法体内部的类不属于朋友类。</span></div></li><li><div><span style="font-size: 12pt;">一个类只和朋友交流，不与陌生类交流，不要出现getA().getB().getC().getD()这种情况（在一种极端的情况下允许出现这种访问，即每一个点号后面的返回类型都相同），类与类之间的关系是建立在类间的，而不是方法间，因此一个方法尽量不引入一个类中不存在的对象，当然，JDK API提供的类除外。</span></div></li><li><div><span style="font-size: 12pt;">朋友间也是有距离的【能不暴露尽量不要暴露】</span></div></li><li><div><span style="font-size: 12pt;">迪米特法则就是对这个距离进行描述，即使是朋友类之间也不能无话不说，无所不知。</span></div></li><li><div><span style="font-size: 12pt;">一个类公开的public属性或方法越多，修改时涉及的面也就越大，变更引起的风险扩散也就越大。因此，为了保持朋友类间的距离，在设计时需要反复衡量：是否还可以再减少public方法和属性，是否可以修改为private、package-private（包类型，在类、方法、变量前不加访问权限，则默认为包类型）、protected等访问权限，是否可以加上final关键字等。</span></div></li><li><div><span style="font-size: 12pt;">迪米特法则要求类“羞涩”一点，尽量不要对外公布太多的public方法和非静态的public变量，尽量内敛，多使用private、package-private、protected等访问权限。</span></div></li><li><div><span style="font-size: 12pt;">是自己的就是自己的</span></div></li><li><div><span style="font-size: 12pt;">如果一个方法放在本类中，既不增加类间关系，也对本类不产生负面影响，那就放置在本类中。</span></div></li><li><div><span style="font-size: 12pt;">谨慎使用Serializable</span></div></li><li><div><span style="font-size: 12pt;">在实际应用中，这个问题是很少出现的，即使出现也会立即被发现并得到解决。</span></div></li><li><div><span style="font-size: 12pt;">迪米特法则的核心观念就是类间解耦，弱耦合，只有弱耦合了以后，类的复用率才可以提高。其要求的结果就是产生了大量的中转或跳转类，导致系统的复杂性提高，同时也为维护带来了难度。</span></div></li><li><div><span style="font-size: 12pt;">在采用迪米特法则时需要反复权衡，既做到让结构清晰，又做到高内聚低耦合。</span></div></li><li><div><span style="font-size: 12pt;">在实际应用中，如果一个类跳转两次以上才能访问到另一个类，就需要想办法进行重构了。</span></div></li><li><div><span style="font-size: 12pt;">一个系统的成功不仅仅是一个标准或是原则就能够决定的，有非常多的外在因素决定，跳转次数越多，系统越复杂，维护就越困难，所以只要跳转不超过两次都是可以忍受的，这需要具体问题具体分析。</span></div></li><li><div><span style="font-size: 12pt;">迪米特法则要求类间解耦，但解耦是有限度的，</span><span style="font-size: 12pt;">在实际的项目中，需要适度地考虑这个原则，别为了套用原则而做项目。原则只是供参考，如果违背了这个原则，项目也未必会失败，这就需要在采用原则时反复度量，不遵循是不对的，严格执行就是“过犹不及”。</span></div></li><li><div><span style="font-size: 12pt;">在将迪米特法则运用到系统设计中时，要注意下面的几点：</span></div></li><li><div><span style="font-size: 12pt;">在类的划分上，应当尽量创建松耦合的类，类之间的耦合度越低，就越有利于复用，一个处在松耦合中的类一旦被修改，不会对关联的类造成太大波及；</span></div></li><li><div><span style="font-size: 12pt;">在类的结构设计上，每一个类都应当尽量降低其成员变量和成员函数的访问权限；</span></div></li><li><div><span style="font-size: 12pt;">在类的设计上，只要有可能，一个类型应当设计成不变类；</span></div></li><li><div><span style="font-size: 12pt;">在对其他类的引用上，一个对象对其他对象的引用应当降到最低。</span></div></li><li><div><span style="font-size: 16px;">这个原则减少了对象之间的依赖，研究显示这会减少软件的维护成本，但是采用这个原则也会导致更多的“包装”类被制造出来，以处理和其他组件的沟通，这可能会导致复杂度和开发时间的增加，并降低运行时的性能。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">七、合成复用原则</span></div><ul><li><div><span style="font-size: 16px;">合成复用原则（Composite Reuse Principle，CRP）</span><span style="font-size: 12pt;">又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle, CARP)</span></div></li><li><div><span style="font-size: 16px;">定义</span></div></li><li><div><span style="font-size: 12pt;">尽量使用对象组合，而不是继承来达到复用的目的</span></div></li><li><div><span style="font-size: 12pt;">复用时要尽量使用组合/聚合关系（关联关系），少用继承</span></div><div><span style="font-size: 12pt; color: unset; font-family: unset;">合成复用原则就是在一个新的对象里通过关联关系（包括组合关系和聚合关系）来使用一些已有的对象，使之成为新对象的一部分；新对象通过委派调用已有对象的方法达到复用功能的目的。</span></div></li><li><div><span style="font-size: 16px;">尽管继承为例强大，但是它便能够不总是能够实现最有弹性和最好维护的设计。利用组合和委托可以在运行时具有继承行为的效果。</span></div><div><span style="font-size: 16px;">利用继承设计子类的行为，是在编译时静态决定的，而且所有的子类都会继承到相同的行为。然而，如果能够利用组合的做法扩展对象的行为，就可以在运行时动态的进行扩展。</span></div></li><li><div><span style="font-size: 16px;">通过动态的组合对象，可以写新的代码添加新的功能，而无须修改现有代码。既然没有改变现有代码，那么引进Bug或产生意外副作用的机会将大幅减少。</span></div></li><li><div><span style="font-size: 12pt;">在面向对象设计中，可以通过两种方法在不同的环境中复用已有的设计和实现</span></div></li><li><div><span style="font-size: 12pt;">通过组合/聚合关系</span></div></li><li><div><span style="font-size: 12pt;">首先应该考虑使用组合/聚合，组合/聚合可以使系统更加灵活，降低类与类之间的耦合度，一个类的变化对其他类造成的影响相对较少；</span></div></li><li><div><span style="font-size: 12pt;">由于组合或聚合关系可以将已有的对象（也可称为成员对象）纳入到新对象中，使之成为新对象的一部分，因此新对象可以调用已有对象的功能，这样做可以使得成员对象的内部实现细节对于新对象不可见，所以这种复用又称为“黑箱”复用。</span></div></li><li><div><span style="font-size: 12pt;">相对继承关系而言，</span><span style="font-size: 12pt;">合成复用</span><span style="font-size: 12pt;">耦合度相对较低，成员对象的变化对新对象的影响不大，可以在新对象中根据实际需要有选择性地调用成员对象的操作。</span></div></li><li><div><span style="font-size: 12pt;">合成复用可以在运行时动态进行，新对象可以动态地引用与成员对象类型相同的其他对象。</span></div></li><li><div><span style="font-size: 12pt;">通过继承</span></div></li><li><div><span style="font-size: 12pt;">其次才考虑继承，在使用继承时，需要严格遵循里氏代换原则，有效使用继承会有助于对问题的理解，降低复杂度，而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度，因此需要慎重使用继承复用。</span></div></li><li><div><span style="font-size: 12pt;">通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性，因为继承会将基类的实现细节暴露给子类，由于基类的内部细节通常对子类来说是可见的，</span><span style="font-size: 12pt; color: unset; font-family: unset;">这种复用又称“白箱”复用</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">如果基类发生改变，那么子类的实现也不得不发生改变；</span></div></li><li><div><span style="font-size: 12pt;">从基类继承而来的实现是静态的，不可能在运行时发生改变，没有足够的灵活性。</span></div></li><li><div><span style="font-size: 12pt;">而且继承只能在有限的环境中使用（如类没有声明为不能被继承）。</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">一般而言，如果两个类之间是“Has-A”的关系应使用组合或聚合，如果是“Is-A”关系可使用继承。</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">&quot;Is-A&quot;是严格的分类学意义上的定义，意思是一个类是另一个类的&quot;一种&quot;；</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">&quot;Has-A&quot;则不同，它表示某一个角色具有某一项责任。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">八、松耦合设计原则</span></div><ul><li><div><span style="font-size: 16px;">为了交互对象之间的松耦合设计而努力</span></div></li><li><div><span style="font-size: 16px;">松耦合的设计之所以能让我们建立有弹性的OO系统，能够应对变化，是因为对象之间的互相依赖降到了最低。</span></div></li><li><div><span style="font-size: 16px;">当两个对象之间松耦合，它们依然可以交互，但是不太清楚彼此的细节</span></div></li></ul><div><font style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">九、好莱坞原则</span></font></div><ul><li><div><span style="font-size: 16px;">定义</span></div></li><ul><li><div><span style="font-size: 16px;">别调用（打电话）我们，我们会调用（打电话给）你。</span></div></li></ul><li><div><span style="font-size: 16px;">好莱坞原则提供一种防止“依赖腐败”的方法。</span></div><div><span style="font-size: 16px;">当高层组件依赖低层组件，而低层组件又依赖高层组件，而高层组件又依赖边侧组件，而边侧组件又依赖低层组件时，依赖腐败就发生了。在这种情况下，没有人可以轻易的搞懂系统使如何设计的。</span></div></li><li><div><span style="font-size: 16px;">在好莱坞原则下，我们允许低层组件将自己挂钩到系统上，但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说，高层组件对待低层组件的方式是“别调用我们，我们会调用你”。</span></div></li><li><div><span style="font-size: 16px;">好莱坞原则用在创建框架或者组件上的一种技巧，好让低层组件能够被挂钩进计算中，而且又不会让高层组件依赖低层组件</span></div></li><li><div><span style="font-size: 16px;">好莱坞原则的目标在于解耦</span></div></li><li><div><span style="font-size: 16px;">好莱坞原则教给我们一个技巧，创建一个有弹性的设计，允许低层结构能够互相操作，而又防止其他类太过依赖它们。</span></div></li><li><div><span style="font-size: 16px;">低层组件不是一定不可以调用高层组件中的方法。事实上，低层组件在结束时，常常会调用从超类中继承来的方法。我们需要避免让高层和低层组件之间有明显的环状依赖。</span></div></li><li><div><span style="font-size: 16px;">好莱坞原则告诉我们，将决策权放在高层模块中，以便决定如何以及何时调用低层模块。</span></div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><span style="font-size: 12pt; font-weight: bold;">十、其他相关讨论</span></div><ul><li><div><span style="font-size: 16px; font-style: italic;">对于单元测试的讨论：</span></div></li><li><div><span style="font-size: 12pt; font-style: italic;">在单元测试中，有一句非常有名的话，叫做&quot;Keep the bar green to keep the code clean&quot;，即保持绿条有利于代码整洁，这是什么意思呢？绿条就是Junit运行的两种结果中的一种：要么是红条，单元测试失败；要么是绿条，单元测试通过。一个方法的测试方法一般不少于3种，为什么呢？首先是正常的业务逻辑要保证测试到，其次是边界条件要测试到，然后是异常要测试到，比较重要的方法的测试方法甚至有十多种。</span></div></li><li><div><span style="font-size: 12pt; font-style: italic;">在实际的项目中，一个类一般只有一个测试类，其中可以有很多的测试方法。</span></div></li><li><div><span style="font-size: 16px; font-style: italic;">单元测试是孤立测试。</span></div></li><li><div><span style="font-size: 12pt; font-style: italic;">Web Service开发就应该知道有一个“契约优先”的原则，也就是先定义出WSDL接口，制定好双方的开发协议，然后再各自实现。</span></div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div></div><div><br clear="none"/></div></span>
</div>
<hr>
<a name="27075"/>
<h1>23.访问者（Visitor）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/10/20 15:57</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/11 1:26</i></td></tr>
<tr><td><b>作者：</b></td><td><i>YuGe</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 12pt; font-weight: bold;">一、基本描述</span></div><ul><li><div><span style="font-size: 16px;">表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。</span></div></li><li><div><span style="font-size: 16px;">访问者模式算是Gof中最复杂的模式。</span></div></li><li><div><span style="font-size: 16px;">GoF的一个作者说过：</span><span style="font-size: 16px; font-weight: bold;">大多数时候你并不需要访问者模式，但当一旦你需要访问者模式时，那就是真的需要它了</span><span style="font-size: 16px;">。</span></div></li><li><div><span style="font-size: 16px;">访问者模式的动机是以不同的方式操作复杂的对象结构。</span><span style="font-size: 12pt;">访问者模式是一种较为复杂的行为型设计模式，它包含访问者和被访问元素两个主要组成部分，这些被访问的元素通常具有不同的类型，且不同的访问者可以对它们进行不同的访问操作。</span></div></li><li><div><span style="font-size: 12pt;">访问者必须参观组合内的每个元素；这样的功能是在导游</span><span style="font-size: 12pt; color: unset; font-family: unset;">（ Traverser）对象中，访问者通过导游的引导，收集组合中所有对象的状态。一旦状态被收集了，客户就可以让访问者对状态进行各种操作。当需要新的功能时，只要加强访问者即可。【个人感觉这段话对于理解访问者模式非常重要！】【图中Element中的Accept接口的意思是，我这里可以接受Visitor访问。Visitor的中的VisitElementX接口的意思是，我在这里访问Element，这样Visitor就将</span><span style="font-size: 12pt; color: unset; font-family: unset;">Element中的状态收集到了，然后就可以在Visitor中做一些事情！个人感觉可以另外做，也可以在VisitElementX中做！这已经是变体了，而且还是更向本质侧的变体！</span><span style="font-size: 12pt; color: unset; font-family: unset;">】</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">处理方案</span></span></div></li><ul><li><div><span style="font-size: 16px;">访问者模式将数据结构和作用于结构之上的操作之间的耦合解脱开，使得操作集合可以相对自由的演化。</span></div></li><li><div><span style="font-size: 16px;">访问者模式的目的是要把处理从数据结构分离出来，很多系统可以按照算法和数据结构分开，如果这样的系统有比较稳定的数据结构，又有易于变化的算法时，使用访问者模式是比较合适的，因为访问者模式使得算法操作的增加变得容易。反之，如果这样的系统的数据结构对象易于变化，经常要有新的数据结构对象增加进来，就不适合使用访问者模式。</span></div></li><li><div><span style="font-size: 16px;">将所有的算法操作分散到各种节点类中会导致整个系统难以理解、难以维护和修改。增加新的操作通常需要重新编译所有这些类，如果可以独立的增加新的操作，并且使这些节点类独立于作用于其上的操作，将会更好一些。</span></div></li><li><div><span style="font-size: 12pt;">可以将每一个类中相关的操作包装在一个独立的对象（称为一个 Visitor）中，并在遍历抽象语法树时将此对象传递给当前访问的元素。当一个元素“接受”该访问者时，该元素向访问者发送一个包含自身类信息的请求。该请求同时也将该元素本身作为一个参数。然后访问者将为该元素执行该操作——这一操作以前是在该元素的类中的。</span></div></li><li><div><span style="font-size: 16px;">使用 Visitor 模式，必须定义两个类层次：一个</span><span style="font-size: 12pt;">是访问者层次结构，</span><span style="font-size: 16px;">对应于接受操作的元素，</span><span style="font-size: 12pt;">提供了抽象访问者和具体访问者。</span><span style="font-size: 16px;">另一个</span><span style="font-size: 12pt;">是元素层次结构，</span><span style="font-size: 16px;">对应于定义对元素的操作的访问者</span><span style="font-size: 12pt;">，提供了抽象元素和具体元素</span><span style="font-size: 16px;">。</span><span style="font-size: 12pt;">相同的访问者可以以不同的方式访问不同的元素，相同的元素可以接受不同访问者以不同访问方式访问。</span></div></li><li><div><span style="font-size: 16px;">给访问者类增加一个新的子类即可创建一个新的操作。</span><span style="font-size: 12pt;">增加新的访问者无须修改原有系统，系统具有较好的可扩展性。</span></div></li></ul><li><div><span style="font-size: 16px; font-weight: bold;">双分派（double-dispatch）</span><span style="font-size: 16px;">。</span><span style="font-size: 12pt;">访问者模式允许你不改变类即可有效地增加其上的操作。</span><span style="font-size: 12pt; color: unset; font-family: unset;">为达到这一效果使用了一种称为</span><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">双分派（double-dispatch）</span><span style="font-size: 12pt; color: unset; font-family: unset;">的技术。这是一种很著名的技术。</span><span style="font-size: 12pt; color: unset; font-family: unset;">事实上，一些编程语言甚至直接支持这一技术。而象 C++ 这样的语言支持</span><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">单分派（single-dispatch）</span><span style="font-size: 12pt; color: unset; font-family: unset;">。</span></div></li><ul><li><div><span style="font-size: 12pt; color: unset; font-family: unset;"><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">单分派：</span>在单分派语言中，到底由哪一种操作将来实现一个请求取决于两个方面：该请求的名字和接收者的类型。</span><span style="font-size: 12pt; color: unset; font-family: unset;">最终哪个操作得到执行依赖于请求的种类和接收者的类型两个方面。</span><span style="font-size: 12pt;">单分派语言处理一个操作是根据请求者的名称和接收到的参数决定的，在Java中有静态绑定和动态绑定之说，它的实现是依据重载（overload）和覆写（override）实现的。</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;"><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">双分派：</span>双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型。 Accept 是一个 double-dispatch 操作。它的含义决定于两个类型：Visitor 的类型和 Element 的类型。双分派使得访问者可以对每一个类的元素请求不同的操作。</span><span style="font-size: 12pt; color: unset; font-family: unset;">双分派意味着得到执行的操作决定于请求的种类和两个接收者的类型，它是多分派的一个特例。Java是一个支持双分派的单分派语言。</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;"><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">这是 Visitor 模式的关键所在：</span>得到执行的操作不仅取决于 Visitor 的类型还决定于它访问的 Element 的类型。可以不将操作静态地绑定在 Element 接口中，而将其安放在一个 Visitor中，并</span><span style="font-size: 12pt; color: unset; font-family: unset;">使用 Accept 在运行时进行绑定。扩展 Element 接口就等于定义一个新的 Visitor 子类而不是多个新的 Element 子类。</span></div></li></ul><li><div><span style="font-size: 12pt;">如果要在系统中增加一种新的访问者，无须修改源代码，只要增加一个新的具体访问者类即可，在该具体访问者中封装了新的操作元素对象的方法。</span><span style="font-size: 12pt; font-weight: bold;">从增加新的访问者的角度来看，访问者模式符合“开闭原则”</span><span style="font-size: 12pt;">。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt;">如果要在系统中增加一种新的具体元素，由于原有系统并未提供相应的访问接口，因此必须对原有系统进行修改，在原有的抽象访问者类和具体访问者类中增加相应的访问方法。</span><span style="font-size: 12pt; font-weight: bold;">从增加新的元素的角度来看，访问者模式违背了“开闭原则”</span><span style="font-size: 12pt;">。</span></div><div style="margin-left: 40px;"><span style="font-size: 12pt;">综上所述，<span style="font-size: 12pt; font-weight: bold;">访问者模式与抽象工厂模式类似，对“开闭原则”的支持具有倾斜性，可以很方便地添加新的访问者，但是添加新的元素较为麻烦。</span></span></div><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">适用性</span></span></span></div></li><ul><li><div><span style="font-size: 12pt;">一个对象结构包含很多类对象，它们有不同的接口，而你想对这些对象实施一些依赖于其具体类的操作。</span><span style="font-size: 12pt;">也就说是用迭代器模式已经不能胜任的情景。</span><span style="font-size: 12pt;">在访问者中针对每一种具体的类型都提供了一个访问操作，不同类型的对象可以有不同的访问操作。</span></div></li><li><div><span style="font-size: 12pt;">需要对一个对象结构中的对象进行很多不同的并且不相关的操作，而你想避免让这些操作“污染”这些对象的类，也不希望在增加新的操作时修改这些类。 访问者模式使得我们可以将相关的操作集中起来定义在访问者类中。</span><span style="font-size: 12pt;">对象结构可以被多个不同的访问者类所使用，将对象本身与对象的访问操作分离。</span><span style="font-size: 12pt;">当该对象结构被很多应用共享时，用 Visitor 模式让每个应用仅包含需要用到的操作。</span></div></li><li><div><span style="font-size: 16px;">访问者模式适用于数据结构相对稳定的系统。</span><span style="font-size: 12pt;">定义对象结构的类很少改变，但经常需要在此结构上定义新的操作。改变对象结构类需要重定义堆所有访问者的接口，这可能需要很大的代价。如果对象结构类经常改变，那么可能还是在这些类中定义这些操作较好。</span></div></li><li><div><span style="font-size: 12pt;">总结一下，在这种地方你一定要考虑使用访问者模式：业务规则要求遍历多个不同的对象。这本身也是访问者模式出发点，迭代器模式只能访问同类或同接口的数据，而访问者模式是对迭代器模式的扩充，可以遍历不同的对象，然后执行不同的操作，也就是针对访问的对象不同，执行不同的操作。</span></div></li><li><div><span style="font-size: 12pt;">访问者模式是一种集中规整模式，特别适用于大规模重构的项目，在这一个阶段需求已经非常清晰，原系统的功能点也已经明确，通过访问者模式可以很容易把一些功能进行梳理，达到最终目的——功能集中化。</span></div></li></ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">实现考虑</span></span></div></li><ul><li><div><span style="font-size: 12pt;">每一个对象结构将有一个相关的 Visitor 类。这个抽象的访问者类为定义对象结构的每一个 ConcreteElement 类声明一个 VisitConcreteElement 操作。每一个 Visitor 上的 Visit 操作声明它的参数为一个特定的 ConcreteElement，以允许该 Visitor 直接访问 ConcreteElement 的接口。</span><span style="font-size: 12pt; color: unset; font-family: unset;">ConcreteVistor 类重定义每一个 Visit 操作，从而为相应的 ConcreteElement 类实现与特定访问者相关的行为。</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">每个 ConcreteElement 类实现一个 Accept 操作，这个操作调用访问者中相应于本 ConcreteElement 类的 Visit 的操作。这样最终得到调用的操作不仅依赖于该元素的类也依赖于访问者的类。</span></div><ul><ul><li><div><span style="font-size: 12pt;">谁负责遍历对象结构。</span><span style="font-size: 12pt;">一个访问者必须访问这个对象结构的每一个元素。我们可以将遍历的责任放到下面三个地方中的任意一个：对象结构中，访问者中，或一个独立的迭代器对象中。</span></div></li><ul><li><div><span style="font-size: 12pt;">通常由对象结构负责迭代。一个集合只需对它的元素进行迭代，并对每一个元素调用 Accept 操作。而一个复合通常让 Accept 操作遍历该元素的各子构件并对它们中的每一个递归地调用 Accept。</span></div></li><li><div><span style="font-size: 12pt;">另一个解决方案是使用一个迭代器来访问各个元素。</span></div></li><li><div><span style="font-size: 12pt;">甚至可以将遍历算法放在访问者中，尽管这样将导致对每一个聚合 ConcreteElement，在每一个 ConcreteVisitor 中都要复制遍历的代码。将该遍历策略放在访问者中的主要原因是想实现一个特别复杂的遍历，它依赖于对该对象结构的操作结果。</span></div></li></ul><li><div><span style="font-size: 16px;">可以同组合模式(Composite)联用：访问者可以用于对一个由 Composite 模式定义的对象结构进行操作。</span></div></li><li><div><span style="font-size: 16px;">可以同解释器模式（Interpreter）联用：访问者可以用于解释。</span></div></li><li><div><span style="font-size: 16px;">通常 ConcreteVisitor 可以单独开发，不必跟 ConcreteElementA 或 ConcreteElementB 写在一起，正因为这样，ConcreteVisitor 能提高 ConcreteElement 之间的独立性，如果将一个处理动作设计成 ConcreteElementA 和 ConcreteElementB 类的方法，每次想新增处理以扩充功能时，就要去修改 </span><span style="font-size: 16px;">ConcreteElementA 和 ConcreteElementB 了。</span></div></li></ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">实际应用</span></span></div></li><ul><li><div><span style="font-size: 12pt;">《大话设计模式》：</span><span style="font-size: 16px;">事实上，我们很难找到数据结构不变化的情况，所以用访问者模式的机会也就不太多了。访问者模式的能力与复杂性是把双刃剑，只有当你真正需要它时，才考虑使用它。有很多程序员为了展示自己面向对象的能力或是沉迷于模式当中，往往会误用这个模式，所以一定要好好理解它的适用性。</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 12pt;">《设计模式之禅》：</span><span style="font-size: 12pt;">访问者模式是经常用到的模式，虽然你不注意，有可能你起的名字也不是什么Visitor，但是它确实是非常容易使用到的。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">《Design Pattern Java》：</span><span style="font-size: 12pt;">由于访问者模式的使用条件较为苛刻，本身结构也较为复杂，因此在实际应用中使用频率不是特别高。当系统中存在一个较为复杂的对象结构，且不同访问者对其所采取的操作也不相同时，可以考虑使用访问者模式进行设计。</span></div><div style="margin-left: 80px;"><span style="font-size: 12pt;">【这一点上《设计模式之禅》和《大话设计模式》这两本书描述不一致，</span><span style="font-size: 12pt;">《设计模式之禅》的就是上面这个说法，是访问者模式经常用到，但是</span><span style="font-size: 12pt;">《大话设计模式》说访问者模式并不是经常用到。个人比较偏向</span><span style="font-size: 12pt;">《大话设计模式》当中的说法，访问者模式在需要的时候是真有用，但是在绝大多数场景下，并没有那么适用。</span><span style="font-size: 12pt;">】</span></div><ul><ul><li><div><span style="font-size: 12pt;">访问者模式还可以充当过滤器或者拦截器（Interceptor）角色。</span></div></li><li><div><span style="font-size: 12pt;">在XML文档解析、编译器的设计、复杂集合对象的处理等领域访问者模式得到了一定的应用。</span></div></li><li><div><span style="font-size: 16px;">当你想要为一个对象的组合增加新的能力，且封装并不重要时，就使用访问者模式。</span></div></li></ul></ul><div><span style="font-size: 12pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="0.设计模式专题_files/Image [52].png" type="image/png" data-filename="Image.png" width="432"/></div><ul><li><div><span style="font-size: 16px; font-weight: bold;">Visitor（抽象访问者）：</span></div></li><ul><li><div><span style="font-size: 12pt;">为该对象结构中 ConcreteElement 的每一个类声明一个 Visit 操作。该操作的名字和特征标识了发送 Visit 请求给该访问者的那个类。这使得访问者可以确定正被访问元素的具体的类。这样访问者就可以通过该元素的特定接口直接访问它。</span></div></li><li><div><span style="font-size: 16px;">抽象类或者接口，声明放着这可以访问哪些元素，具体到程序中就是 Visit 方法的参数定义哪些对象可以被访问的【实际上就是确定接口】</span></div></li><li><div><span style="font-size: 12pt;">抽象访问者定义了访问元素对象的方法，通常为每一种类型的元素对象都提供一个访问方法，而具体访问者可以实现这些访问方法。这些访问方法的命名一般有三种方式：</span></div></li><ul><li><div><span style="font-size: 12pt;">一种是直接在方法名中标明待访问元素对象的具体类型，如visitElementA(ElementAelementA)；</span></div></li><li><div><span style="font-size: 12pt;">还有一种是统一取名为visit()，通过参数类型的不同来定义一系列重载的visit()方法。</span><span style="font-size: 16px;">有几个元素就有几个访问方法，可以利用重载函数来实现接口：都是 visit 方法，传入的参数不一样。</span></div></li><li><div><span style="font-size: 12pt;">当然，如果所有的访问者对某一类型的元素的访问操作都相同，则可以将操作代码移到抽象访问者类中。</span></div></li></ul></ul><li><div><span style="font-size: 16px; font-weight: bold;">ConcreteVisitor（具体访问者）：</span></div></li><ul><li><div><span style="font-size: 12pt;">实现每个由 Visitor 声明的操作。每个操作实现本算法的一部分，而该算法片断乃是对应于结构中对象的类。 ConcreteVisitor 为该算法提供了上下文并存储它的局部状态。</span><span style="font-size: 12pt; color: unset; font-family: unset;">这一状态常常在遍历该结构的过程中累积结果。</span></div></li><li><div><span style="font-size: 16px;">影响访问者访问到一个类后怎么干，要做什么事情。每一个操作用于访问对象结构中的一种类型的元素。</span></div></li><li><div><span style="font-size: 16px;">个人理解</span></div></li><ul><li><div><span style="font-size: 16px;">算法：一个 ConcreteVisitor 是一个算法。</span></div></li><li><div><span style="font-size: 16px;">算法片段：ConcreteVisitor 中的一个函数。</span></div></li><li><div><span style="font-size: 16px;">结构中对象的类：一个 ConcreteVisitor 的一个方法对应一个 ConcreteElement 对象。</span></div></li><li><div><span style="font-size: 16px;">这里的结构指的是对象结构： ObjectStructure，即元素的集合。</span></div></li><li><div><span style="font-size: 16px;">遍历该结构：遍历的是 ObjectStructure </span></div></li></ul></ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">Element（抽象元素）：</span></span></div></li><ul><li><div><span style="font-size: 16px;">定义一个 Accept 操作，它以一个访问者为参数。</span></div></li><li><div><span style="font-size: 12pt;">接口或抽象类，声明接受哪一类访问者访问，程序上是通过 accept 方法中的参数来定义的。</span></div></li><li><div><span style="font-size: 16px;">抽象元素有两类方法：</span></div></li><ul><li><div><span style="font-size: 16px;">一是本身的业务逻辑，也就是元素作为一个业务处理单元必须完成的职责（Operation）。</span></div></li><li><div><span style="font-size: 16px;">另一个是允许哪一个访问者来访问（Accept），这一方法充分利用双分派技术，实现处理与数据结构的分离。</span></div></li></ul></ul></ul><div style="margin-left: 120px;"><span style="font-size: 16px;">Accept 方法的意义就是说我这个类可以允许谁来访问，也就是定义一类访问者，在具体的实现类中调用访问者的方法。</span></div><div style="margin-left: 120px;"><span style="font-size: 12pt;">需要注意的是该方法（</span><span style="font-size: 16px;">Accept 方法</span><span style="font-size: 12pt;">）传入了一个抽象访问者 Visitor 类型的参数，即针对抽象访问者进行编程，而不是具体访问者，在程序运行时再确定具体访问者的类型，并调用具体访问者对象的 visit() 方法实现对元素对象的操作。在抽象元素类 Element 的子类中实现了 accept() 方法，用于接受访问者的访问，在具体元素类中还可以定义不同类型的元素所特有的业务方法。</span></div><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">ConcreteElement（具体元素）：</span></span></div></li><ul><li><div><span style="font-size: 16px;">实现 Accept 操作，该操作以一个访问者为参数。</span></div></li><li><div><span style="font-size: 12pt;">在具体元素类 ConcreteElement 的 accept() 方法中，通过调用 Visitor 类的 visit() 方法实现对元素的访问，并以当前对象作为 visit() 方法的参数。其具体执行过程如下（双重分派）：</span></div></li><ul><li><div><span style="font-size: 12pt;">调用具体元素类的 accept(Visitor visitor) 方法，并将 Visitor 子类对象作为其参数；</span></div></li><li><div><span style="font-size: 12pt;">在具体元素类 accept(Visitor visitor) 方法内部调用传入的 Visitor 对象的 visit() 方法，如 visit(ConcreteElement element)，将当前具体元素类对象(this)作为参数，如 visitor.visit(this)；</span></div></li><li><div><span style="font-size: 12pt;">执行 Visitor 对象的 visit() 方法，在其中还可以调用具体元素对象的业务方法。</span></div></li></ul></ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">ObjectStructure（对象结构）：</span></span></div></li><ul><li><div><span style="font-size: 16px;">能枚举它的元素。</span></div></li><li><div><span style="font-size: 16px;">可以提供一个高层的接口以允许该访问者访问它的元素。</span></div></li><li><div><span style="font-size: 16px;">可以是一个组合（组合模式）或是一个集合，如一个列表或一个无序集合。</span><span style="font-size: 16px;">用于存放元素对象，并且提供了遍历其内部元素的方法，可以结合组合模式实现，也可以是一个简单的集合对象。</span></div></li><li><div><span style="font-size: 16px;">仅仅有 Element 还不行，还需要有一个程序将其组装成一个集合。</span><span style="font-size: 16px;">在使用访问者模式时，被访问元素通常不是单独存在的，它们存储在一个集合中，这个集合被称为“对象结构”，访问者通过遍历对象结构实现对其中存储的元素的逐个操作。</span></div></li><li><div><span style="font-size: 16px;">元素产生者，一般容纳在多个不同类，不同接口的容器，如 List、Map 等，在项目中，可能不用抽象出这个角色。</span></div></li><li><div><span style="font-size: 16px;">一般情况下，</span><span style="font-size: 12pt;">在对象结构中可以使用迭代器对存储在集合中的元素对象进行遍历，并逐个调用每一个对象的accept()方法，实现对元素对象的访问操作。</span></div></li></ul><li><div><span style="font-size: 12pt; font-weight: bold;">协作：</span></div></li><ul><li><div><span style="font-size: 12pt;">一个使用 Visitor 模式的客户必须创建一个 ConcreteVisitor 对象，然后遍历该对象结构，并用该访问者访问每一个元素。</span></div></li><li><div><span style="font-size: 16px;">当一个元素</span><span style="font-size: 12pt;">被访问时，它调用对应于它的类的 Visitor 操作。如果必要，该元素将自身作为这个操作的一个参数以便该访问者访问它的状态。</span></div></li><li><div><span style="font-size: 16px;">下图描述了一个对象结构、一个访问者和两个元素之间的协作。</span></div></li></ul></ul><div style="text-align: center;"><img src="0.设计模式专题_files/Image [53].png" type="image/png" data-filename="Image.png" width="362"/></div><div><span style="font-size: 12pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-size: 12pt; font-weight: bold;">优点</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">访问者模式使得易于增加新的操作</span>。访问者使得增加依赖于复杂对象结构的构件的操作变得容易了。仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。相反，如果每个功能都分散在多个类之上的话，定义新的操作时必须修改每一类。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt; color: unset; font-family: unset;"><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">具有优秀的扩展性</span>。增加新的操作就意味着增加一个新的访问者，访问者模式将有关的行为集中到一个访问者对象中。</span><span style="font-size: 12pt;">通过增加访问者，只要是具体元素就非常容易访问，对元素的遍历就更加容易了，甭管它是什么对象，只要它在一个容器中，都可以通过访问者来访问，任务集中化。由于职责分开，继续增加对数据的操作是非常快捷的。</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">访问者集中相关的操作而分离无关的操作</span>。相关的行为不是分布在定义该对象结构的各个类上，而是集中在一个访问者中。无关行为却被分别放在它们各自的访问者子类中。这就既简化了这些元素的类，也简化了在这些访问者中定义的算法。所有与它的算法相关的数据结构都可以被隐藏在访问者中。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt; font-weight: bold;">符合单一职责原则</span><span style="font-size: 12pt;">。</span><span style="font-size: 12pt;">具体元素角色 ConcreteElement 负责数据的加载，而 Visitor 类则负责算法的展现，两个不同的职责非常明确地分离开来，各自演绎变化。</span></div><div style="margin-left: 40px;"><span style="font-size: 12pt;">类的职责更加清晰，有利于对象结构中元素对象的复用，相同的对象结构可以供多个不同的访问者访问。</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">通过类层次进行访问</span>。一个迭代器</span><span style="font-size: 12pt;">可以通过调用节点对象的特定操作来遍历整个对象结构，同时访问这些对象。但是迭代器不能对具有不同元素类型的对象结构进行操作。访问者没有这种限制，它可以访问不具有相同父类的对象。可以对一个 Visitor 接口增加任何类型的对象。</span></div><div><span style="font-size: 12pt;">让用户能够在不修改现有元素类层次结构的情况下，定义作用于该层次结构的操作。</span></div></li><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">累积状态</span>。当访问者访问对象结构中的每一个元素时，它可能会累积状态。如果没有访问者，这一状态将作为额外的参数传递给进行遍历的操作，或者定义为全局变量。</span></div></li><li><div><span style="font-size: 12pt;">灵活性非常高。</span></div></li><li><div><span style="font-size: 12pt;">允许你对组合结构加入新的操作，而无需改变结构本身。</span></div></li><li><div><span style="font-size: 12pt;">访问者所进行的操作，其代码是集中在一起的。【都集中到访问者类中】</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">缺点</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">增加新的 ConcreteElement 类很困难</span>。Visitor 模式使得难以增加新的 Element 的子类。每添加一个新的 ConcreteElement 都要在 Vistor 中添加一个新的抽象操作，并在每一个 ConcretV</span><span style="font-size: 12pt; color: unset; font-family: unset;">isitor 类中实现相应的操作。有时可以在 Visitor 中提供一个缺省的实现，这一实现可以被大多数的 ConcreteVisitor 继承，但这与其说是一个规律还不如说是一种例外。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt; color: unset; font-family: unset;"><span style="font-size: 12pt; color: unset; font-family: unset; font-weight: bold;">所以在应用访问者模式时考虑关键的问题是系统的哪个部分会经常变化，是作用于对象结构上的算法呢还是构成该结构的各个对象的类</span>。如果老是有新的 ConcretElement 类加入进来的话，Vistor 类层次将变得难以维护。在这种情况下，直接在构成该结构的类中定义这些操作可能更容易一些。如果 Element类层次是稳定的，而你不断地增加操作获修改算法，访问者模式可以帮助你管理这些改动。</span></div><div style="margin-left: 40px;"><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">具体元素变更比较困难</span>。</span><span style="font-size: 12pt;">具体元素角色的增加、删除、修改都是比较困难的。<br/></span><span style="font-size: 12pt;">因为游走的功能牵涉其中，所以对组合结构的改变就更加困难</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">破坏封装</span>。访问者方法假定 ConcreteElement 接口的功能足够强，足以让访问者进行它们的工作。结果是，该模式常常迫使你提供访问元素内部状态的公共操作，这可能会破坏它的封装性。</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">具体元素对访问者公布细节</span>。</span><span style="font-size: 12pt;">访问者模式要求访问者对象访问并调用每一个元素对象的操作，</span><span style="font-size: 12pt;">这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态，</span><span style="font-size: 12pt;">访问者要访问一个类就必然要求这个类公布一些方法和数据，也就是说访问者关注了其他类的内部细节，</span><span style="font-size: 12pt;">否则无法供访问者访问。</span><span style="font-size: 12pt;">这是迪米特法则所不建议的。<br/></span><span style="font-size: 12pt;">当采用访问者模式的时候，就会打破组合类的封装。</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">违背了依赖倒置转原则</span>。</span><span style="font-size: 12pt;">访问者依赖的是具体元素，而不是抽象元素，这破坏了依赖倒置原则，特别是在面向对象的编程中，抛弃了对接口的依赖，而直接依赖实现类，扩展比较难。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">四、可能的改进</span></div><ul><li><div><span style="font-size: 16px;">在实际的项目中，一个对象，多个访问者的情况非常多。</span></div></li><li><div><span style="font-size: 12pt;"><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/visitor/sample01" style="font-size: 12pt;">可以利用“xml + 反射”机制改进观察者模式。在 xml 当中指定使用哪个具体观察者。</a></span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">五、个人理解</span></div><ul><li><div><span style="font-size: 16px;">访问者模式比较复杂，但不会经常使用，针对元素固定的场景可以考虑。</span></div></li><li><div><span style="font-size: 16px;">双分派的引入让元素与操作互相依赖，除非自动生成代码，否则手写观察者模式感觉并不好。</span></div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br/></div><div><br/></div><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="28104"/>
<h1>0-1.模式对比</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/12/2 6:07</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/31 7:00</i></td></tr>
<tr><td><b>作者：</b></td><td><i>YuGe</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">工厂方法模式 vs. 抽象工厂模式</span></span></div><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">联系</span></span></div><ul><li><div><span style="font-size: 16px;">抽象工厂的每个方法实际上看起来都像是工厂方法。每个方法都被声明成抽象，而子类的方法覆盖这些方法来创建某些对象。</span></div><div><span style="font-size: 12pt; color: unset; font-family: unset;">抽象工厂的方法经常以工厂方法的方式实现。抽象工厂的任务是定义一个负责创建一组产品的接口，这个接口内的每个方法都负责创建一个具体产品，同时我们利用实现抽象工厂的子类来提供这些具体的做法。所以，在抽象工厂中利用工厂方法实现生产方法是相当自然的做法。</span></div><div><span style="font-size: 12pt; color: unset; font-family: unset;">抽象工厂</span><span style="font-size: 12pt; color: unset; font-family: unset;">的具体工厂经常实现工厂方法来创建他们的产品。对</span><span style="font-size: 12pt; color: unset; font-family: unset;">抽象工厂</span><span style="font-size: 12pt; color: unset; font-family: unset;">来说，这些具体工厂纯粹只是用来创建产品罢了。</span></div><div><span style="font-size: 12pt; color: unset; font-family: unset;">【观察在家族中的一个具体的产品，都是使用工厂方法】</span></div></li><li><div><span style="font-size: 16px;">工厂方法与抽象工厂都能将对象的创建封装起来，使应用程序解耦，并降低其对特定实现的依赖。</span></div></li></ul><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">区别</span></span></div><ul><li><div><span style="font-size: 16px;">工厂方法使用的是类，抽象工厂使用的是对象</span></div><div><span style="font-size: 12pt;">抽象工厂与</span><span style="font-size: 16px;">工厂方法</span><span style="font-size: 12pt;">都是负责创建对象，</span><span style="font-size: 12pt;">但是：</span></div><div><span style="font-size: 16px;">工厂方法使用</span><span style="font-size: 12pt;">继承创建对象，把对象的创建委托给子类，子类实现工厂方法来创建对象。</span></div><div><span style="font-size: 12pt;">抽象工厂是通过对象的组合创建对象，对象的创建被实现在工厂接口所暴露出来的方法中。</span></div></li><li><div><span style="font-size: 16px;">实现方式不同</span></div><div><span style="font-size: 12pt;">利用工厂方法创建对象，需要扩展一个类，并覆盖它的工厂方法。</span></div><div><span style="font-size: 12pt;">抽象工厂提供一个用来创建一个产品家族的抽象类型，这个类型的子类定义了产品被产生的方法。要想使用这个工厂，必须先实例化它，然后将它传入一些针对抽象类型所写的代码中。所以，和工厂方法一样，</span><span style="font-size: 12pt;">抽象工厂</span><span style="font-size: 12pt;">可以把客户从所使用的实际具体产品中解耦。</span><span style="font-size: 12pt;">抽象工厂</span><span style="font-size: 12pt;">另一个优点是可以把一群相关的产品集合起来。</span></div></li><li><div><span style="font-size: 16px;">接口复杂性不同</span></div><div><span style="font-size: 12pt;">工厂方法</span><span style="font-size: 12pt;">是创建一个产品，所以</span><span style="font-size: 12pt;">工厂方法</span><span style="font-size: 12pt;">根本不需要一个大的接口，只需要一个方法就可以了。</span></div><div><span style="font-size: 12pt;">抽象工厂</span><span style="font-size: 12pt;">需要一个大的接口，因为</span><span style="font-size: 12pt;">抽象工厂</span><span style="font-size: 12pt;">可是被用来创建整个产品家族的。需要扩展这组相关产品（如新增一个产品），就必须改变接口。</span></div></li><li><div><span style="font-size: 16px;">工厂方法提供一个抽象接口来创建“一个产品”，由每个子类来决定实例化哪个具体类。</span></div><div><span style="font-size: 16px;">抽象工厂提供一个抽象接口用来创建一个产品家族，每个具体子类都创建一个家族的产品，这些负责在抽象工厂中创建产品的方法，通常是以“工厂方法”来实现的，这些产品子类创建了一组平行的产品家族。</span></div></li><li><div><span style="font-size: 16px; color: unset; font-family: unset; font-weight: bold;">应用</span></div></li><ul><li><div><span style="font-size: 12pt;">抽象工厂：当需要创建产品家族和想让制造的相关产品集合起来时，可以使用</span></div></li><li><div><span style="font-size: 12pt;">工厂方法：可以把客户代码从需要实例化的具体类中解耦。或者如果目前还不知道将来需要实例化哪些具体类时，也可以用</span><span style="font-size: 12pt;">工厂方法</span><span style="font-size: 12pt;">。</span><span style="font-size: 12pt;">工厂方法</span><span style="font-size: 12pt;">的使用方式很简单，只要</span><span style="font-size: 12pt;">继承抽象的Creator，并实现</span> <span style="font-size: 16px; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; color: rgb(0, 0, 0); font-family: 微软雅黑; font-variant-caps: normal; font-variant-ligatures: normal;">Creator</span><span style="font-size: 12pt;">的工厂方法就可以了。</span></div></li></ul></ul><div><br clear="none"/></div><div><span style="font-size: 12pt; font-weight: bold;">策略模式 vs. 状态模式</span></div><div><font style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">联系</span></font></div><ul><li><div><span style="font-size: 16px;">类图一样</span></div></li></ul><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">区别</span></span></div><ul><li><div><span style="font-size: 16px;">意图不同</span></div></li><ul><li><div><span style="font-size: 16px;">状态模式</span></div><div><span style="font-size: 12pt;">我们将一群行为封装在状态对象中， context的行为随时可委托到那些状态对象中的一个。随着时间的流逝，当前状态在状态对象集合中游走改变，以反映出 context内部的状态，因此， context的行为也会跟着改变。但是 context的客户对于状态对象了解不多，甚至根本是浑然不觉。</span></div><div><span style="font-size: 12pt;">我们把状态模式想成是不用在 context中放置许多条件判断的替代方案。通过将行为包装进状态对象中，你可以通过在 context内简单地改变状态对象来改变 context的行为。</span></div></li><li><div><span style="font-size: 16px;">策略模式</span></div><div><span style="font-size: 12pt;">客户通常主动指定 Context所要组合的策略对象是哪一个。现在，固然策略模式让我们具有弹性，能够在运行时改变策略，但对于某个 context对象来说，通常都只有一个最适当的策略对象。</span></div><div><span style="font-size: 12pt;">一般来说，我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为，你将被这个行为困住，甚至要修改它都很难。有了策略模式，你可以通过组合不同的对象来改变行为。</span></div></li></ul></ul><div><font style="font-size: 12pt;"><br clear="none"/></font></div><div><font style="font-size: 12pt;"><b>代理模式 vs. 策略模式</b></font></div><div><font style="font-size: 12pt;"><b>联系</b></font></div><div><font style="font-size: 12pt;"><b>区别</b></font></div><ul><li><div><span style="font-size: 16px;">目的不同</span></div><div><span style="font-size: 16px;">装饰者模式为对象增加行为</span></div><div><span style="font-size: 16px;">代理模式控制对象的访问</span></div></li></ul><div><font style="font-size: 12pt;"><br clear="none"/></font></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="29472"/>
<h1>更加深入</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2021/1/20 6:11</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/11 1:06</i></td></tr>
<tr><td><b>作者：</b></td><td><i>YuGe</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 18pt;">定义设计模式</span></div><ul><li><div>模式是在某情境（context）下，针对某问题的某种解决方案。</div></li><ul><li><div><span style="font-weight: bold;">情境</span>就是应用某个模式的情况，这应该是会不断出现的情况。</div></li><li><div><span style="font-weight: bold;">问题</span>就是你想在某情境下达到的目标，但也可以是某情境下的约束。</div></li><li><div><span style="font-weight: bold;">解决方案</span>就是你所追求的：一个通用的设计，用来解决约束、达到目标。</div></li></ul><li><div>如果你发现自己处于某个情境下，面对着所欲达到的目标被一群约束影响着的问题，然而，你能够应用某个设计，克服这些约束并达到该目标，将你领向某个解决方案。</div></li><li><div>一个设计模式是解决一个经常重复发生的设计问题。</div></li><li><div>我们采用一种规矩的方式描述模式，就能为模式创建出“类目”。而这个类目能为我们带来各种好处。</div></li><li><div>模式应该具备的几个条件：</div></li><ul><li><div>必须能够应用于一个重复出现的问题。</div></li><li><div>其他人应该也可以应用这个模式来解决相同的问题。</div></li><li><div>模式应该拥有名字：如果没有名字，一个模式就无法变成开发人员之间共享的词汇。</div><div>名称非常重要！当每个模式都有一个名称的时候，我们谈论起模式来就相当容易了；也就是说，大家会有一个共享的词汇。</div></li></ul><li><div>真实世界中的很多实例，都不符合经典的设计模式。模式只是指导方针，可以改变模式来符合自己的需要。</div><div>然而，当你在改变模式的时候，最好能够在文档中注明它与经典的设计模式有何差异。这样一来，其他的开发人员就能够很快地认出你用的这个模式，并了解两者的差异。</div></li></ul><div><span style="font-size: 18pt;">设计模式的类目</span></div><ul><li><div>描述一个模式：模式类目：描述某个模式的意图、动机、可能应用该模式的地方、解决方案的设计以及使用后果（好的或坏的）。</div></li><li><div>如何获得模式类目：</div></li><ul><li><div>《设计模式：可复用面向对象软件的基础》列出了23个基本模式的类目。</div></li><li><div>还有许多其他将焦点放在不同领域（例如：企业软件、并发系统、业务系统）的模式类目。</div></li></ul><li><div>类目内容：</div><div>每个模式的描述方式都遵照一个模板，并阐述该模式的许多细节。</div></li><ul><li><div>名称</div></li><li><div>意图：简短的描述该模式的作用。描述该模式是什么，有点像定义，可以理解成定义。</div></li><li><div>动机：给出了问题以及如何解决这个问题的具体场景。</div></li><li><div>适用性：描述模式可以被应用在什么场合。</div></li><li><div>结构：提供了图示（类图、对象图等），显示出参与此模式的类之间的关系。描述类图内的所有组成模式的类的设计，以及每个类扮演的角色。</div></li><li><div>参与者：描述在此设计中涉及到的类和对象在模式中的责任和角色。</div></li><li><div>协作：告诉我们参与者如何在此模式中合作。</div></li><li><div>结果：描述采用此模式之后可能产生的效果：好的与不好的。</div></li><li><div>实现：提供了你在实现该模式时需要使用的技巧，以及你应该小心面对的问题。</div></li><li><div>范例代码：提供代码的片段，可能对自己的实现有帮助。描述如何实现这个模式，而且通常由展示怎么做的范例代码。</div></li><li><div>已知应用：描述已经在真实系统中发现的模式例子。告诉我们在真实的系统中，这个模式会使用在何处。</div></li><li><div>相关模式：描述了此模式和其他的模式之间的关系。</div></li></ul><li><div>如何使用类目</div></li><ul><li><div><span style="font-size: unset; color: unset; font-family: unset;">遇到问题时，如何翻阅类目寻找解决方案：</span></div></li><ul><li><div>首先，试着让自己熟悉所有的模式以及它们之间的关系。</div></li><li><div>然后，当需要一个模式的时候，大概就知道是什么模式。应参考描述动机和适用性的小节，确认的想法没错。还有一个很重要的小节：结果。浏览这个模式的“结果”，确保该模式不会给我的设计带来意外的影响。</div></li></ul><li><div>一旦知道某个模式是正确的，对这个模式的应用和实现：</div></li><ul><li><div>此时需要类图。先是阅读“结构”这一节，以了解类图，然后看“参与者”这节，确定我了解每一个类的角色。</div></li><li><div>接下来，就可以开始进行自己的设计，做出符合自己需求的一些更改，并继续阅读“实现/范例代码”小节，以确认我知道可能会遇到的所有较好的实现技巧。</div></li></ul></ul></ul><div><span style="font-size: 18pt;">发掘自己的设计模式</span></div><ul><li><div>首先，请务必牢记在心，模式是被“发现的”，而不是被创建的。所以，任何人都可能发现某个设计模式，然后写出它的描述。</div></li><li><div>应有意愿：你应该先想想看为何你想发现自己的模式—一大多数的人都不是“编写”模式，只是使用模式。</div></li><ul><li><div>你可能是在某一个特定的领域中工作，而你认为新的模式将大有帮助</div></li><li><div>你找到个解决方案，能够解决一个再三出现的问题</div></li><li><div>你只是想要加入设计模式的社群贡献自己的力量。</div></li></ul><li><div>如何开始</div></li><ul><li><div>就像任何原则一样，你知道得越多越好。</div><div>先研究已经被发现的这些模式，了解它们做了些什么，并弄清楚它们和其他模式之间的关系。这些准备工作非常重要，不但可以让你熟悉模式是如何打造出来的，也可以避免做多余的工作。</div></li><li><div>完成这些准备工作之后，你可以开始将你的模式写在纸上，以便与其他开发人员沟通。</div></li></ul><li><div>如何知道自己是否真的发现了一个设计模式：</div></li><ul><li><div>除非其他人使用它并且发现它很有用，否则你并不算拥有一个模式。</div></li><li><div>一般来说，必须要通过“三次规则”，才算是一个合格的模式。也就是说，只有在真实的世界中被应用三次以上，才能算是一个模式。</div></li></ul><li><div>发掘自己的设计模式的方法和步骤</div></li><ul><li><div>做好家庭作业</div><div>在发掘新的模式之前，你必须先精通现有的模式。许多模式看起来像是全新的，但是事实上只是现有模式的变种。通过研究现有的模式，你可以比较容易地识别模式，并且学会将某一模式与其他模式联系起来。</div></li><li><div>花时间反思与评估</div></li><ul><li><div>反思：你的经验（你所遭遇过的问题，以及采取的解决方案）正是模式想法的来源。所以，花时间反思过去的经验，并将它用在以后的新设计上面。</div></li><li><div>评估：请牢记，大多数的模式都是现有模式的变种，而非崭新的模式。而且当你真的找到了好像是新模式的东西时，常常都局限在很窄的适用性中，而不能称得上是一个真正的模式。</div></li></ul><li><div>将你的想法写在纸上，好让其他人能够理解</div><div>如果其他人不能够使用你所找到的模式，那么这个新模式作用也就不大；你需要将你的“准模式”写成一份文档，好让其他人能够阅读、理解，并采用它来解决他们自己的问题，然后将使用的心得反馈给你。</div><div>很幸运的是，你不需要发明自己的模式归档方法，你可以直接采用四人组的模板</div></li><li><div>让其他人使用你的模式，然后再持续改进</div><div>不要认为你可以一次就把模式搞定，应该要把模式当成是随着时间不断进步的一项工程。让其他人评审你的准模式，并尝试着使用它，然后将意见反馈给你。将这些反馈汇总到你的描述中，再重复上述的步骤。你的描述永远不会是完美的，但是到了某个时间点之后，就会相当地稳固，足以让其他开发人员能够阅读并理解它。</div><div>使用已有的模式模板定义你的模式，因为这些模板已经包含了许多智慧，而且其他的模式用户也认识这样的格式。</div></li><li><div>不要忘了三次原则</div><div>请记住，除非你的模式已经在真实世界的三个方案中被成功地采用了，否则就不够资格被当成模式。所以，当别人能够使用你的模式，并将意见反馈给你时，你就有机会能够将它变成一个实用的模式。</div></li></ul></ul><div><span style="font-size: 18pt;">设计模式的分类</span></div><ul><li><div>随着发掘的设计模式数目逐渐增加，有必要将它们分级分类，好将它们组织起来，以简化我们寻找模式的过程，并让同一群组内的模式互相比较。</div></li><li><div>模式的意图并不相同，而这通常是了解某个模式属于哪个类目时的关键。</div></li><li><div>类目可以让我们思考模式群组之间的关系，以及同一组模式内模式之间的关系。</div></li><li><div>根据模式的目标分成三个不同类目：创建型、行为型和结构型。</div></li><ul><li><div>创建型</div><div>创建型模式涉及到将对象实例化，这类模式都提供一个方法，将客户从所需要实例化的对象中解耦。</div></li><li><div>行为型</div><div>只要是行为型模式，都涉及到类和对象如何交互及分配职责。</div><div>行为型模式的目的是对象之间的沟通与互连。</div></li><li><div>结构型</div><div>结构型模式可以让你把类或对象组合到更大的结构中。</div><div>结构型模式用来描述类和对象如何被组合以建立新的结构或新的功能。</div></li></ul><li><div>模式还有另一种分类方式：模式所处理的是类或对象。</div></li><ul><li><div>类模式</div><div>类模式描述类之间的关系如何通过继承定义。类模式的关系是在编译时建立的。</div></li><li><div>对象模式</div><div>对象模式描述对象之间的关系，而且主要是利用组合定义。对象模式的关系通常在运行时建立，而且更加动态、更有弹性。</div></li><li><div>对象模式比类模式的数目多出许多。</div></li></ul><li><div>如果建立自己的分类方式可以帮你更加了解这些模式的话，那么你也可以这么做。</div></li><li><div>通过比较可让你对模式有清晰的概念，这是毋庸置疑的。但是许多人被创建型、结构型和行为型类目搞得一头雾水，常常发现某个模式似乎不只适合一个类目。请记住，怎么分类并不重要，重要的是了解这些模式和它们之间的关系。只要类目有帮助，我们就用它，反之就不用。</div></li><li><div>通过分类，我们可以将一组模式视为一个群体。当我们需要一个创建型模式，但又不知道确切是哪一个的时候，就可以用创建型模式这个词来统称它。</div><div>而且分类也有助于我们比较相同类目内的其他成员。</div></li></ul><div><span style="font-size: 18pt;">使用设计模式的方法</span></div><ul><li><div>下面是一份快速指南，可以帮助你开始“用模式思考”。所谓“用模式思考”，意思是说，能够看着设计，体会在什么地方模式能自然适用，在什么地方模式则不能。</div></li><ul><li><div>保持简单（ Keep It Simple/KISS）</div><div>首先，<span style="font-weight: bold;">当你设计时，尽可能地用最简单的方式解决问题。你的目标应该是简单，</span>而不是“如何在这个问题中应用模式”。千万不要认为：如果没有使用模式解决某个问题，就不是经验丰富的开发人员。正确的说法是，为了要让你的设计简单且有弹性，有时候使用模式是最好的方法。</div></li><li><div>设计模式非万是丹；事实上，连什么丹都算不上！</div><div>模式是解决一再发生的问题的通用方案。模式已经被许多开发人员实际测试过。所以，当你需要某个模式的时候，可以放心地使用它，毕竟你知道这个模式已经身经百战。</div><div>然而，模式并非万灵丹，要使用模式，你需要考虑到模式对你的设计中其他部分所造成的后果。</div></li><li><div>需要知道何时需要模式</div><div>当你在设计的时候，如果确定在你的设计中可以利用某个模式解决某个问题，那么就使用这个模式！如果有更简单的解决方案，那么在决定使用模式之前应该先考虑这个方案。</div><div>如何知道何时适用一个模式，这就需要经验和知识。一旦你确定一个简单的解决方案无法满足你的需要，应该考虑这个问题以及相关的约束——这可以帮你将问题对应到一个模式中。如果你对于模式有很深的认知，就可能知道有什么模式适合这样的情况。否则，就花些时间调査一下可能会解决这个问题的模式，模式类目中的意图和应用部分会特别有用。一旦找到了一个看起来适合的模式，要先确定你是否能接受这个模式所带来的后果，以及对设计其他部分的影响。</div><div>有一种情况，即使有更简单的解决方案，你仍然想要使用模式，这种情况就是：你预期系统在未来会发生改变。找出你的设计中会改变的区域，通常这是需要模式的迹象。但是务必要确定一件事：加入模式是要应对可能发生的实际改变，而不是假想的改变。</div><div><span style="font-size: unset; color: unset; font-family: unset;">并非只有在设计时才考虑引进模式，在重构（ refactoring）时也要这样做！</span></div></li><li><div>重构的时间就是模式的时间！</div><div>重构就是通过改变你的代码来改进它的组织方式的过程。目标是要改善其结构，而不是其行为。这是一个很好的时机，可以重新检查你的设计来看看是否能够利用模式让它拥有更好的结构。</div></li><li><div>拿掉你所不需要的，不要害将一个<span style="font-size: unset; color: unset; font-family: unset;">设</span>计模式从你的设计中删除。</div><div>当你的系统变得非常复杂，而且并不需要预留任何弹性的时候，就不要使用模式。换句话说，也就是当一个较简单的解决方案比使用模式更恰当的时候。</div></li><li><div>如果你现在不需要，就别做。</div><div><span style="font-size: unset; color: unset; font-family: unset;">设计模式威力很强大，你很容易就可以在当前设计中看到模式的各种应用方式。开发人员天生就热爱创建漂亮的架构以应对任何方向的改变。</span></div><div><span style="font-size: unset; color: unset; font-family: unset;">但如果你今天在设计中有实际的需要去支持改变，就放手采用模式处理这个改变吧！然而，如果说理由只是假想的，就不要添加这个模式，因为这只会将你的系统越搞越复杂，而且很可能你永远都不会需要它！</span></div><div>将你的思绪集中在设计本身，而不是在模式上。只有在真正需要时才使用模式。有些时候，简单的方式就行得通，那么就别用模式。</div></li></ul><li><div>模式只是一种工具，只有在需要时才使用这种工具。你也花了很多时间学习设计原则。一开始总是先遵循这些原则，建立最简单的代码以完成工作。<span style="font-size: unset; color: unset; font-family: unset;">在这个过程中，你看到有需要模式的地方，就使用模式。</span></div></li><li><div>“应用模式”绝对不是你开始设计时所该有的目标，应该让模式在你的设计过程中自然而然地出现。</div></li><li><div>模式可能带来复杂性，如果没有必要，我们绝不需要这样的复杂性。</div></li><li><div>当你确信你的设计中有一个问题需要解决的时候，或者当你确信未来的需求可能会改变时，都可以采用模式。</div></li><li><div>过度使用设计模式可能导致代码被过度工程化。<span style="font-weight: bold;">应该总是用最简单的解决方案完成工作，并在真正需要模式的地方才使用它。</span></div></li><li><div>设计模式不仅可以帮助你在大脑中装进这些解决方案，也可以让你和其他开发人员之间有共享的词汇，而这正是设计模式最大的优点之一。</div><div>共享词汇的五种方式</div></li><ul><li><div>在设计会议中：当你和你的团队在会议中讨论软件设计时，使用设计模式可以帮你们待在“设计中”久一点。从设计模式和面向对象原则的视角讨论设计，可以避免你的团队很快地陷入实现的细节，也可以避免发生许多误解。</div></li><li><div>和其他开发人员：当你和其他开发人员讨论的时候，可以使用模式。这可以帮助其他开发人员学习新模式，并建立一个社群。和别人分享你所学会的东西是很有成就感的一件事情。</div></li><li><div>在架构文档中：当你在编写架构文档的时候，使用模式将会缩减文档的篇幅，并且让读者更清楚地了解你的设计。</div></li><li><div>在代码注释以及命名习惯上：当你在编写代码的时候，应在注释中清楚地注明你所使用的模式。在选择类和方法的名称时，应尽可能显示出隐藏在下面的模式。其他的开发人员在阅读你的代码时会感激你，因为你让他们能够很快地了解你的实现。</div></li><li><div>将志同道合的开发人员集合在一起：分享你的知识。许多开发人员都听说过模式，但并不真正了解什么是模式。你可以自愿为他们讲一堂模式介绍课，或者成立一个读书会。</div></li></ul></ul><div><span style="font-size: 24px;">反模式</span></div><ul><li><div>反模式告诉你如何采用一个不好的解决方案解决一个问题。</div></li><li><div>反模式看起来总像是个好的解决方案，但是当它真正被采用后，就会带来麻烦。</div></li><li><div>通过将反模式归档，我们能够帮助其他人在实现它们之前，分辨出不好的解决方案。</div></li><li><div>如果老是有人用某个不好的解决方案处理某个问题，而通过将它归档，可以帮助其他开发人员避免犯同样的错误。毕竟，避免不好的解决方案，就和发现好的解决方案一样有价值！</div></li><li><div>一个反模式的元素：</div></li><ul><li><div>反模式告诉我们为什么不好的解决方案会有吸引力。</div></li><li><div>必须面对的是，如果不好的解决方案没有任何吸引力，那么根本就不会有人想要使用它。反模式最重要的工作之一，在于警告你不要陷入某种致命的诱惑。</div></li><li><div>反模式告诉你为何这个解决方案从长远看会造成不好的影响。</div></li><li><div>为了了解为什么这是一个反模式，你必须了解它在将来如何造成负面影响。反模式会告诉你使用这个解决方案，在将来会为你带来怎样的麻烦。</div></li><li><div>反模式建议你改用其他的模式以提供更好的解决方案。</div></li><li><div>反模式除了告诉你什么解决方案不好之外，也会为你指出正确的方向，向你建议一些会引向好的解决方案的可能性，这样反模式才真正有帮助。</div></li></ul></ul><div><span style="font-size: 24px;">个人想法</span></div><ul><li><div>很多设计模式都是通过增加一个中间层来解决问题！</div></li><li><div>说实话感觉自己只是知道了设计模式，但是对设计模式的理解并不深，最关键的是总感觉并没有理解每种设计模式的核心。就连常用的设计模式的核心也没有理解！比如为什么这么称呼，应用场景的关键是什么，使用的关键特征是什么等。如果不理解这些，就没有办法做调整，做针对应用场景的修正。</div></li></ul><div><br/></div></div><div><br/></div></span>
</div>
<hr>
<a name="30365"/>
<h1>24.复合模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2021/3/1 7:31</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/5 7:13</i></td></tr>
<tr><td><b>作者：</b></td><td><i>YuGe</i></td></tr>
</table>
</div>
<br/>

<div><span><ul><li><div>概述</div></li><ul><li><div>使用模式最棒的方式，就是让它们同其他模式展开交互</div></li><li><div>这是一种由模式组成的模式</div></li><li><div>将某些模式结合使用，并不代表这些模式能称为复合模式。复合模式必须够一般性，适合解决许多问题才行。</div></li><li><div>MVC就是一个真正的复合模式。它是由数个模式结合起来而形成的新模式，一再的被用于解决许多设计问题。</div></li><li><div>模式通常被一起使用，并被组合在同一个设计解决方案中。</div></li><li><div>复合模式在一个解决方案中结合两个或多个模式，以解决一般或重复发生的问题。</div></li><li><div>在OO设计的过程中，折衷一直都是避免不了的。</div></li><li><div>采用模式时必须要考虑到这么做是否有意义。绝对不能为了使用模式而使用模式。有时候，用好的OO设计原则就可以解决问题，这样其实就够了。</div></li></ul><li><div>MVC模式</div></li><ul><li><div>MVC：Model-View-Controller，模型-视图-控制器</div></li><li><div>模型和视图分处两边，中间是控制器</div></li><li><div>模型和视图之间的数据流动，是由控制器居中协调进行，两者之间状态的改变，数据的同步，都是由控制器控制的</div></li><li><div>设计模式是学习MVC的钥匙：MVC是由数个设计模式结合起来的模式，是一种复合模式。</div></li><li><div>控制器位于视图和模型之间。它将用户的输入转给模型做动作。</div></li><li><div>MVC三者关系如下：</div></li></ul></ul><div><img src="0.设计模式专题_files/Image [54].png" type="image/png" data-filename="Image.png" width="524"/><br/></div><div><img src="0.设计模式专题_files/Image [55].png" type="image/png" data-filename="Image.png" width="506"/><br/></div><ul><li><div>在某些设计中，控制器可以变成模型的观察者，控制器会向模型注册，模型一有改变就通知控制器。当模型直接影响到用户界面时，就会这么做。比方说，模型内的些状态可以支配界面的些项目变成有效或无效，如果这样，要求视图更相应显示其实就是控制器的事。</div></li><li><div>控制器做的事情不只有“发送给模型”，还会解读输入，并根据输入操纵模型。</div><div>问题是“为何不能把这样的代码放在视图中？”</div><div>当然可以这么做，但是你不想这么做。有两个原因：首先，这会让视图的代码变得复杂，因为这样一来视图就有两个责任，不但要管理用户界面，还要处理如何控制模型的逻辑。第二个原因，这么做将造成模型和视图之间紧耦合，如果你想复用此视图来处理其他模型，根本不可能。控制器把控制逻从视图中分离，让模型和视图之间解耦。通过保持控制器和视图之间松耦合，设计就更有弹性而且容易扩展，足以容纳以后的改变。</div></li><li><div>个人理解，就是在传统的视图和模型之间增加一层中间层——控制器！</div></li><li><div>学会MVC最好的方法就是看看它是由哪些模式共同组成的：</div></li></ul><div><img src="0.设计模式专题_files/Image [56].png" type="image/png" data-filename="Image.png" width="443"/><br/></div><ul><ul><li><div>观察者模式：模型利用“观察者”让控制器和视图可以随最新的状态改变而更新。</div></li></ul></ul><div><img src="0.设计模式专题_files/Image [57].png" type="image/png" data-filename="Image.png" width="500"/><br/></div><ul><ul><li><div>策略模式：视图和控制器实现了策略模式，控制器是视图的行为，如果希望有不同的行为，则可以直接换一个控制器。</div></li></ul></ul><div><img src="0.设计模式专题_files/Image [58].png" type="image/png" data-filename="Image.png" width="574"/><br/></div><ul><ul><li><div>组合模式：视图内部使用组合模式来管理窗口、按钮以及其他显示组件。</div></li></ul></ul><div><img src="0.设计模式专题_files/Image [59].png" type="image/png" data-filename="Image.png" width="549"/><br/></div><ul><li><div>个人理解：按照《Head First设计模式》书中的描述：视图只是单纯的界面，只管展示；模型是数据结构以及底层逻辑；控制器在视图一侧，控制视图的逻辑。</div></li><li><div>对于设计MVC中的视图，一般要将模型的视图与控制的视图分离！</div></li><li><div>适配器模式是使用MVC时经常附带用到的技巧：使用适配器将模型适配成复合现有视图和控制器的需要的模型。</div></li><li><div>控制器为视图实现行为。它聪明的将来自视图的动作转成模型上的动作。模型实现应用逻辑，并决定如何响应动作。控制器需要做的决定是调用哪个模型的哪个方法。</div></li></ul><div><br/></div></span>
</div>
<hr>
<a name="30480"/>
<h1>25.其他设计模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2021/3/4 6:21</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/4 6:23</i></td></tr>
<tr><td><b>作者：</b></td><td><i>YuGe</i></td></tr>
</table>
</div>
<br/>

<div><span><ul><li><div>不变（<span style="color: rgb(51, 51, 51); font-family: &quot;PingFang SC&quot;, &quot;Lantinghei SC&quot;, &quot;Microsoft Yahei&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft Sans Serif&quot;, &quot;WenQuanYi Micro Hei&quot;, Helvetica, sans-serif; font-size: 16px; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;">Immutable</span>）模式</div></li><ul><li><div><span style="color: rgb(51, 51, 51); font-family: &quot;PingFang SC&quot;, &quot;Lantinghei SC&quot;, &quot;Microsoft Yahei&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft Sans Serif&quot;, &quot;WenQuanYi Micro Hei&quot;, Helvetica, sans-serif; font-size: 16px; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;">Immutable 模式，中文叫作不变模式，它并不属于经典的 23 种设计模式，但作为一种较常用的设计思路，可以总结为一种设计模式来学习</span></div></li><li><div><span style="font-size: 16px; color: rgb(51, 51, 51); font-family: &quot;PingFang SC&quot;;">一个对象的状态在对象创建之后就不再改变，这就是所谓的不变模式。其中涉及的类就是不变类（Immutable Class），对象就是不变对象（Immutable Object）。在 Java 中，最常用的不变类就是 String 类，String 对象一旦创建之后就无法改变。</span></div></li><li><div><span style="color: rgb(51, 51, 51); font-family: &quot;PingFang SC&quot;, &quot;Lantinghei SC&quot;, &quot;Microsoft Yahei&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft Sans Serif&quot;, &quot;WenQuanYi Micro Hei&quot;, Helvetica, sans-serif; font-size: 16px; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;">不变模式可以分为两类，一类是普通不变模式，另一类是深度不变模式（Deeply Immutable Pattern）。普通的不变模式指的是，对象中包含的引用对象是可以改变的。如果不特别说明，通常我们所说的不变模式，指的就是普通的不变模式。深度不变模式指的是，对象包含的引用对象也不可变。它们两个之间的关系，有点类似之前讲过的浅拷贝和深拷贝之间的关系</span></div></li><li><div><span style="color: rgb(51, 51, 51); font-family: &quot;PingFang SC&quot;, &quot;Lantinghei SC&quot;, &quot;Microsoft Yahei&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft Sans Serif&quot;, &quot;WenQuanYi Micro Hei&quot;, Helvetica, sans-serif; font-size: 16px; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;">在某个业务场景下，如果一个对象符合创建之后就不会被修改这个特性，那我们就可以把它设计成不变类。显式地强制它不可变，这样能避免意外被修改。那如何将一个类设置为不变类呢？其实方法很简单，只要这个类满足：所有的成员变量都通过构造函数一次性设置好，不暴露任何 set 等修改成员变量的方法。除此之外，因为数据不变，所以不存在并发读写问题，因此不变模式常用在多线程环境下，来避免线程加锁。所以，不变模式也常被归类为多线程设计模式。</span></div></li></ul></ul><div><br/></div></span>
</div></body></html> 